Extending $log to log to database in AngularJS

$log is a very useful built-in service that is part of the AngularJS (angular 1) distribution. It is even more useful if you could write logs to a database table rather than just to console.

Here’s how you can do it:

Assumes you have a RESTful back-end that responds to the url in the log() function of LoggingService below,


var app = angular.module('app');

app.factory("LoggingService", function ($http) {
    var _isEnabled = true;

    var isEnabled = function () {
        return _isEnabled;
    }

    var enable = function (b) {
        _isEnabled = b;
    }

    var log = function () {
        var url = "/Api/Audit/Log";
        if (this.isEnabled()) {
            var type = "UNK"; // placeholder for when no type is passed
            args = [];
            if (typeof arguments === 'object') {
                type = arguments[0];
                for (var i = 1; i < arguments.length; i++) {
                    arg = arguments[i];
                    if (typeof (arg) != 'string') arg = JSON.stringify(arg);
                    args.push(arg);
                }
                return $http.post(url, { "ACTIVITY": type + ': ' + args.join('\n') });
            }
            else {
                return $http.post(url, { "ACTIVITY": type + ': ' + arguments[0] });
            }
        }
    };

    return {
        log: log,
        enable: enable,
        isEnabled: isEnabled
    };
})

.config(function ($routeProvider, $logProvider, $httpProvider, $provide) {

    $logProvider.debugEnabled(1);

    // -----------------------------------------------------------------------------------
    // logging algorithm suggested by:
    // http://stackoverflow.com/questions/32365811/decorate-angulars-log-to-use-a-service-that-uses-http
    // -----------------------------------------------------------------------------------

    $provide.decorator("$log", function ($delegate, $injector) {

        var logFn = $delegate.log;

        // N.B. Don't delegate $log.debug()...that is ALWAYS written just to the console!
        // $log.warn(), $log.error() and $log.info() are written to both console (assuming $logProvider.debugEnabled == 1) and database
        // $log.db() is written ONLY to database!!

        $delegate.warn = function (message) {
            var LoggingService = $injector.get('LoggingService');
            LoggingService.log('WARN', message);
            if ($logProvider.debugEnabled()) logFn.apply(null, arguments);
        };
        $delegate.error = function (message) {
            var LoggingService = $injector.get('LoggingService');
            LoggingService.log('ERROR', message);
            if ($logProvider.debugEnabled()) logFn.apply(null, arguments);
        };
        $delegate.info = function (message) {
            var LoggingService = $injector.get('LoggingService');
            LoggingService.log('INFO', message);
            if ($logProvider.debugEnabled()) logFn.apply(null, arguments);
        };
        $delegate.db = function (message) {
            var LoggingService = $injector.get('LoggingService');
            LoggingService.log('DB', message);
            // N.B. $log.db is not written to console ever!!
        };
        //Return the delegate
        return $delegate;
    });
//   :
//   :
// more config functionality here
//   :
//   :
}) // end of config function

.run();

RESTful Angular 2 services

Sample RESTful service for use with Angular 2:

 

import { Injectable } from '@angular/core';
import { Http, Response, Headers } from '@angular/http';
import 'rxjs/add/operator/map'
import { Observable } from 'rxjs/Observable';

/*
database User table structure:
    ID INTEGER NOT NULL, AUTOINCREMENT
    NAME VARCHAR(50) NOT NULL
    PHONE VARCHAR(20) NOT NULL
*/

export class User {
    public id:number;
    public name:string;
    public phone:string;
}

@Injectable()
export class UserService {

    private actionUrl: string;
    private headers: Headers;

//
// sample url for a web service ==> "http://somesite.com/user/"
//

    constructor(private http: Http, url:string) {

        this.actionUrl = url;

        this.headers = new Headers();
        this.headers.append('Content-Type', 'application/json');
        this.headers.append('Accept', 'application/json');
    }

    public GetUsers = (): Observable<Response> => {
        return this.http.get(this.actionUrl).map(res => res.json());
    }

    public GetUser = (id: number): Observable<Response> => {
        return this.http.get(this.actionUrl + id).map(res => res.json());
    }

    public InsertUser = (user: User): Observable<Response> => {
        var toAdd = JSON.stringify({ User: user });

        return this.http.post(this.actionUrl, toAdd, 
                    { headers: this.headers }).map(res => res.json());
    }

    public UpdateUser = (id: number, itemToUpdate: User): Observable<Response> => {
        return this.http.put(this.actionUrl + id, JSON.stringify(itemToUpdate), 
                    { headers: this.headers }).map(res => res.json());
    }

    public DeleteUser = (id: number): Observable<Response> => {
        return this.http.delete(this.actionUrl + id);
    }
}

Tunneling PUT, DELETE and HEAD methods as POSTs

Applies to: Asp.net MVC WebAPI projects

This post is adapted from Hanselman.

Step 1. Add the following MethodOverrideHandler.cs class in the project root:

    public class MethodOverrideHandler : DelegatingHandler
    {
        readonly string[] _methods = { "DELETE", "HEAD", "PUT" };
        const string _header = "X-HTTP-Method-Override";

        protected override Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request, CancellationToken cancellationToken)
        {
            // Check for HTTP POST with the X-HTTP-Method-Override header.
            if (request.Method == HttpMethod.Post && request.Headers.Contains(_header))
            {
                // Check if the header value is in our methods list.
                var method = request.Headers.GetValues(_header).FirstOrDefault();
                if (_methods.Contains(method, StringComparer.InvariantCultureIgnoreCase))
                {
                    // Change the request method.
                    request.Method = new HttpMethod(method);
                }
            }
            return base.SendAsync(request, cancellationToken);
        }
    }

 

Step 2. In the Register method of App_Start/WebApiConfig.cs, insert the following code:

 

    config.MessageHandlers.Add(new MethodOverrideHandler());

 

Step 3. You’ll need to modify any client calls of PUT, DELETE or HEAD that access your WebApi project. For each affected call, add the following header:

'X-HTTP-Method-Override': 'PUT'

or
'X-HTTP-Method-Override': 'DELETE'

or
'X-HTTP-Method-Override': 'HEAD'

…and then make your AJAX call as a POST, adding whatever parameters you need either as part of the URL or as data.

Now your PUT, DELETE or HEAD calls will pass through the firewall as POSTs, be recognized by a message handler for what they truly are and converted to their native method, then passed to your Controllers to be executed as expected.