Programmers frequently use console.log to record errors or other informational messages in their Angular applications. Although this is fine while debugging your application, it's not a best practice for production applications. As Angular is all about services, it's a better idea to create a logging service that you can call from other services and components. In this logging service, you can still call console.log, but you can also modify the service later to record messages to store them in local storage or a database table via the Web API.
In this article, you'll build up the logging service in a series of steps. First, you create a simple log service class to log messages using console.log()
. Next, you add some logging levels so you can report on debug, warning, error, and other types of log messages. Then you create a generic logging service class to call other classes to log to the console, local storage, and a Web API. Finally, you create a log publishing service that reads a JSON file to choose which log service classes to use.
A Simple Logging Service
To get started, create a very simple logging service that only logs to the console. The point here is to replace all of your console.log statements in your Angular application with calls to a service. Bring up an existing Angular application or create a new one. If you don't already have one, add a folder named shared
under the \src\app
folder. Create a new TypeScript file named log.service.ts
. Add the code shown in the following snippet.
import { Injectable } from '@angular/core';
@Injectable()
export class LogService {
log(msg: any) {
console.log(new Date() + ": " + JSON.stringify(msg));
}
}
This code creates an injectable service that can be created by Angular and injected into any of your Angular classes. The log()
method accepts a message that can be any type. A new date is created so each message can be logged to the console with the date and time attached to it. The date/time is not that important when just logging to the console, but once you start logging to local storage or to a database, you want the date/time attached so you know when the log messages was created. Notice the use of JSON.stringify
around the msg
parameter. This allows you to pass an object and it can be logged as a string.
For the purpose of following along with this article, create a new folder called \log-test
and add a log-test.component.html
page. Add a button to test the logging service.
<button (click)="testLog()">Log Test</button>
Create a log-test.component.ts
TypeScript file and add the code shown in Listing 1 to respond to the button click event.
Listing 1: The LogTestComponent to test the LogService class
import { Component } from "@angular/core";
import { LogService } from '../shared/log.service';
@Component({
selector: "log-test",
templateUrl: "./log-test.component.html"
})
export class LogTestComponent {
constructor(private logger: LogService) {
}
testLog(): void {
this.logger.log("Test the `log()` Method");
}
}
Add a logger variable to your constructor so Angular can inject this service into this component. Notice that in the testLog()
method you now call the this.logger.log()
instead of console.log()
. The result is the same (See Figure 1) in that the message appears in the console window. However, you've now given yourself the flexibility to log this message to local storage, to a database table, to the console, or to all three. And, the best part is, you don't have to change any code in your application, other than the code in the LogService
class. To use this service in your Angular application, you need to import it in your app.module.ts
file. Also import the LogTestComponent
you created as well.
import { LogService }
from './shared/log.service';
import { LogTestComponent }
from './log-test/log-test.component';
Add the LogService
to the providers property in the @NgModule
statement. Add the LogTestComponent
class to the declarations property, as shown in the code snippet below.
@NgModule({
imports: [BrowserModule],
declarations: [AppComponent, LogTestComponent],
bootstrap: [AppComponent],
providers: [LogService]
})
export class AppModule { }
Add the <log-test></log-test>
selector on one of your pages to display the Log Test button. Run the application and click on the Log Test button and you should see the message appear in the console window in the F12 tools of your browser, as shown in Figure 1.
Different Types of Logging
There are times when you might want only certain types of logging turned on when running your application. Many logging systems in other languages allow you to log debug messages, informational messages, warning messages, etc. Add this same ability to your LogService
class by adding an enumeration and a property that you can set to control which messages to display. First, add a LogLevel enumeration in the log.service.ts
file to keep track of what kind of logging to perform. Add this enumeration just after the import statement within the log.service.ts
file. Don't add it within the LogService
class.
export enum LogLevel {
All = 0,
Debug = 1,
Info = 2,
Warn = 3,
Error = 4,
Fatal = 5,
Off = 6
}
Add a property to the LogService
class named level
that's of the type LogLevel
. Default the value to the All enumeration. While you are adding properties, add a Boolean property named logWithDate
to specify whether you wish to add the date/time to the front of your messages or not.
level: LogLevel = LogLevel.All;
logWithDate: boolean = true;
Instead of having to set the level
property prior to calling your logger.log()
method, add the new methods debug, info, warn, error, and fatal to the LogService
class (Listing 2). Each one of these methods calls a writeToLog()
method passing in the message, the appropriate enumeration value and an optional parameter array. Delete the log()
method you wrote previously and replace that one method with all the methods from Listing 2.
Listing 2: Add methods to your LogService class to write different kinds of messages
debug(msg: string, ...optionalParams: any[]) {
this.writeToLog(msg, LogLevel.Debug, optionalParams);
}
info(msg: string, ...optionalParams: any[]) {
this.writeToLog(msg, LogLevel.Info, optionalParams);
}
warn(msg: string, ...optionalParams: any[]) {
this.writeToLog(msg, LogLevel.Warn, optionalParams);
}
error(msg: string, ...optionalParams: any[]) {
this.writeToLog(msg, LogLevel.Error, optionalParams);
}
fatal(msg: string, ...optionalParams: any[]) {
this.writeToLog(msg, LogLevel.Fatal, optionalParams);
}
log(msg: string, ...optionalParams: any[]) {
this.writeToLog(msg, LogLevel.All, optionalParams);
}
The optional parameter array means you can pass any parameters you want to be logged. For example, any of the following calls are valid.
this.logger.log("Test 2 Parameters", "Paul", "Smith");
this.logger.debug("Test Mixed Parameters", true, false, "Paul", "Smith");
let values = ["1", "Paul", "Smith"];
this.logger.warn("Test String and Array", "Some log entry", values);
The writeLog()
method, Listing 3, checks the level passed by one of the methods against the value set in the level
property. This level
property is checked in the shouldLog()
method. Both of these methods should now be added to your LogService
class.
Listing 3: The writeToLog() method creates the message to write into your log
private writeToLog(msg: string, level: LogLevel, params: any[]) {
if (this.shouldLog(level)) {
let value: string = "";
// Build log string
if (this.logWithDate) {
value = new Date() + " - ";
}
value += "Type: " + LogLevel[this.level];
value += " - Message: " + msg;
if (params.length) {
value += " - Extra Info: " + this.formatParams(params);
}
// Log the value
console.log(value);
}
}
The shouldLog()
method determines if logging should occur based on the level
property set in the LogService
class. This service is created as a singleton by Angular, so once this level
property is set, it remains that value until you change it in your application. The shouldLog()
checks the parameter passed in against the level
property set in the LogService
class. If the level passed in is greater than or equal to the level
property, and logging is not turned off, then a true
value is returned from this method. A true
return value tells the writeToLog()
method to log the message.
private shouldLog(level: LogLevel): boolean {
let ret: boolean = false;
if ((level >= this.level && level !== LogLevel.Off) || this.level === LogLevel.All) {
ret = true;
}
return ret;
}
There's one more method call in the writeToLog()
method called formatParams()
. This method is used to create a comma-delimited list of the parameter array. If all parameters in the array are simple data types and not an object, then the local variable named ret
is returned after the join()
method is used to create a comma-delimited list from the array. If there is one object, loop through each of the items in the params array and build the ret
variable using the JSON.stringify()
method to convert each parameter to a string, and then append a comma after each.
private formatParams(params: any[]): string {
let ret: string = params.join(",");
// Is there at least one object in the array?
if (params.some(p => typeof p == "object")) {
ret = "";
// Build comma-delimited string
for (let item of params) {
ret += JSON.stringify(item) + ",";
}
}
return ret;
}
Create LogEntry Class
Instead of building a string of the log information, and formatting the parameters in the writeToLog()
method, create a class named LogEntry
to do all this for you. Place this new class within the log.service.ts
file. The LogEntry
class, shown in Listing 4, has properties for the date of the log entry, the message to log, the log level, an array of extra info to log, and a Boolean you set to specify to include the date with the log message.
Listing 4: Create a LogEntry class to make creating log messages easier
export class LogEntry {
// Public Properties
entryDate: Date = new Date();
message: string = "";
level: LogLevel = LogLevel.Debug;
extraInfo: any[] = [];
logWithDate: boolean = true;
buildLogString(): string {
let ret: string = "";
if (this.logWithDate) {
ret = new Date() + " - ";
}
ret += "Type: " + LogLevel[this.level];
ret += " - Message: " + this.message;
if (this.extraInfo.length) {
ret += " - Extra Info: " + this.formatParams(this.extraInfo);
}
return ret;
}
private formatParams(params: any[]): string {
let ret: string = params.join(",");
// Is there at least one object in the array?
if (params.some(p => typeof p == "object")) {
ret = "";
// Build comma-delimited string
for (let item of params) {
ret += JSON.stringify(item) + ",";
}
}
return ret;
}
}
The buildLogString()
method is similar to what you wrote in the writeToLog()
method earlier. This method gathers the values from the properties of this class and returns them in one long string that can be used to output to the console window. Remove the formatParams()
method from the LogService
class after you've built the LogEntry
class. You're going to use this LogEntry
class from each of the different logging classes you build in the rest of this article.
Now that you have this LogEntry
class built, and have removed the formatParams()
from the LogService
class, rewrite the writeToLog()
method to look like the code below.
private writeToLog(msg: string, level: LogLevel, params: any[]) {
if (this.shouldLog(level)) {
let entry: LogEntry = new LogEntry();
entry.message = msg;
entry.level = level;
entry.extraInfo = params;
entry.logWithDate = this.logWithDate;
console.log(entry.buildLogString());
}
}
After modifying the writeToLog()
method, rerun the application and you should still see your log messages being displayed in the console window.
Log Publishing System
When logging exceptions, or any kind of message, it's a good idea to write those log entries to different locations in case one of those locations isn't accessible. In this way, you stand a better chance of not losing any messages. To do this, you need to create three different classes for logging. The first publisher is a LogConsole
class that logs to the console window. The second publisher is LogLocalStorage to log messages to Web local storage. The third publisher is
LogWebApi` for calling a Web API to log messages to a backend table in a database.
Instead of hard-coding each of these classes in the LogService
class, you're going to create a publishers
property (Figure 2), which is an array of an abstract class called LogPublisher
. Each of the logging classes you create extends this abstract class.
The LogPublisher
class contains one property named location. This property is used to set the key for local storage and the URL for the Web API. This class also needs two methods: log()
and clear()
. The log()
method is overridden in each class that extends LogPublisher
and is responsible for performing the logging. The clear()
method removes all log entries from the data store.
Add a new TypeScript file named log-publishers.ts to the \shared
folder in your project. You're going to need a few import statements at the top of this file. In fact, you're going to add more later, but for now, just add Observable
, the Observable of
, and LogEntry
classes. Write the code shown in the next snippet to create your abstract LogPublisher
class.
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import { LogEntry } from './log.service';
export abstract class LogPublisher {
location: string;
abstract log(record: LogEntry):
Observable<boolean>
abstract clear(): Observable<boolean>;
}
Now that you have the template for creating each log publishing class, let's start building them.
Log to Console
The first class you create to extend the LogPublisher
class writes messages to the console. You're eventually going to remove the call to console.log()
from the LogService
class you created earlier. LogConsole
is a very simple class that displays log data to the console window using console.log()
. Add the following code below the LogPublisher
class in the log-publisher.ts
file.
export class LogConsole extends LogPublisher {
(entry: LogEntry): Observable<boolean> {
// Log to console
console.log(entry.buildLogString());
return Observable.of(true);
}
clear(): Observable<boolean> {
console.clear();
return Observable.of(true);
}
}
Notice that the log()
method in this class accepts an instance of the LogEntry
class. This parameter coming in, named entry
, calls the buildLogString()
method to create a string of the complete log entry data to be displayed to the console window. Each log()
method needs to return an observable of the type Boolean back to the caller. Because nothing can go wrong with logging to the console, just hard-code a True
return value.
The clear()
method must also be overridden in any class that extends the LogPublisher
class. For the console window, call the clear()
method to clear all messages published to the console window.
The Log Publishers Service
As you saw in Figure 2, there's a publishers
array property in the LogService
class. You need to populate this array with instances of LogPublisher
classes. The only class you've built so far is LogConsole
, but soon you'll build LogLocalStorage
and LogWebApi
classes too. Instead of building the list of publishers in the LogService
class, create another service class to build the list of log publishers. This service class, named LogPublishersService
, is responsible for building the array of log publishing classes. This service is passed into the LogService
class so the publishers
array can be assigned from the LogPublishersService
(Figure 3). At first, you're going to just hard-code each of the log classes, but later in this article, you're going to read the list publishers from a JSON file.
The LogPublishersService
class (Listing 5) needs to be defined as an injectable service so Angular can inject it into the LogService
class. In the constructor of this class, you call a method named buildPublishers()
. This method creates each instance of a LogPublisher
and adds each instance to the publishers
array. For now, just add the code to create new instance of the LogConsole
class and push it onto the publishers
array.
Listing 5: The LogPublishersService is responsible for creating a list of publishing objects
import { Injectable } from '@angular/core';
import { LogPublisher, LogConsole } from "./log-publishers";
@Injectable()
export class LogPublishersService {
constructor() {
// Build publishers arrays
this.buildPublishers();
}
// Public properties
publishers: LogPublisher[] = [];
// Build publishers array
buildPublishers(): void {
// Create instance of LogConsole Class
this.publishers.push(new LogConsole());
}
}
Update AppModule Class
Like any Angular service, once you create it, you must register it in the app.module.ts
file by importing it and adding it to the providers property of the @NgModule
. Open app.module.ts
and add the import near the top of the file.
import { LogPublishersService }
from "./shared/log-publishers.service";
Next, add the service to the providers property in the @NgModule
decorator function.
@NgModule({
imports: [BrowserModule, FormsModule, HttpModule],
declarations: [AppComponent, LogTestComponent],
bootstrap: [AppComponent],
providers: [LogService, LogPublishersService]
})
Modify the LogService Class
It's now time to modify the LogService
class to use this LogPublishersService
class. Open the log.service.ts
TypeScript file and add two import statements near the top of the file.
import { LogPublisher } from "./log-publishers";
import { LogPublishersService }
from "./shared/log-publishers.service";
Add a property named publishers
that is an array of LogPublisher
types.
publishers: LogPublisher[];
Add a constructor to the LogService
class so Angular injects the LogPublishersService
. Within this constructor, take the publishers
property from the LogPublishersService
and assign the contents to the publishers
property in the LogService
class.
constructor(private publishersService: LogPublishersService) {
// Set publishers
this.publishers = this.publishersService.publishers;
}
Locate the writeToLog()
method and remove the following line of code from this method.
console.log(entry.buildLogString());
Where you removed the above line of code, add a for loop to iterate over the list of publishers. Each time through the loop, invoke the log()
method of the logger, passing in the LogEntry
object. Because the log()
method returns an observable, you should subscribe to the result and write the Boolean return value to the console window.
for (let logger of this.publishers) {
logger.log(entry).subscribe(response => console.log(response));
}
Once again, run the application and click the Test Log button to see a log entry written to the console window. You have added a few classes and a service only to publish to the console window; you should be able to see the advantages of this kind of approach. You can now add new LogPublisher
classes, add them to the array in the LogPublishersService
class, and you're now publishing to an additional location.
Log to Local Storage
The next publisher to add is one that stores an array of LogEntry
objects into your Web browser's local storage. Open the log-publishers.ts
file and add the code shown in Listing 6 to this file. The LogLocalStorage
class needs to set a key value for setting the items into local storage. Use the location
property to set the key value to use. In this case, the location
is set in the constructor. When you have a constructor in a derived class, you always need to call the super()
method in order to invoke the constructor of the base class.
Listing 6: Create a LogLocalStorageService class to store messages into local storage
export class `LogLocalStorage` extends LogPublisher {
constructor() {
// Must call `super()`from derived classes
super();
// Set location
this.location = "logging";
}
// Append log entry to local storage
log(entry: LogEntry): Observable<boolean> {
let ret: boolean = false;
let values: LogEntry[];
try {
// Get previous values from local storage
values = JSON.parse(localStorage.getItem(this.location)) || [];
// Add new log entry to array
values.push(entry);
// Store array into local storage
localStorage.setItem(this.location, JSON.stringify(values));
// Set return value
ret = true;
} catch (ex) {
// Display error in console
console.log(ex);
}
return Observable.of(ret);
}
// Clear all log entries from local storage
clear(): Observable<boolean> {
localStorage.removeItem(this.location);
return Observable.of(true);
}
}
Local storage allows you to store quite a bit of data, so let's add each log entry each time the log()
method is called. The setItem()
method is used to set a value into local storage. If you call setItem()
and pass in a value, any old value in the key location is replaced with the new value. Read the previous values first from local storage using the getItem()
method. Parse that into an array of LogEntry
objects, or if there was no value stored in that key location, return an empty array. Push the new LogEntry
object onto the array, stringify the new array, and place the stringified array into local storage.
One note of caution on local storage; there's a limit set by each browser to how much data can be stored. The limits vary between browsers, and as of this writing, it varies from 2MB to 10MB. You might want to consider writing some additional code in the catch block of the log()
method to remove the oldest values from the array prior to storing the new log entry.
The clear()
method is used to clear local storage at the specified key location. Call the removeItem()
method of the localStorage
object to clear all values within this location.
Now that you have your new class to store log entries into local storage, you need to add this to the publishers array. Open the log-publishers.service.ts
file and modify the import statement to include your new LogLocalStorage
class.
import { LogPublisher, LogConsole, `LogLocalStorage` } from "./log-publishers";
Modify the buildPublishers()
method of the LogPublishersService
class to create an instance of the LogLocalStorage
class and push it onto the publishers array as shown in the following code.
buildPublishers(): void {
// Create instance of LogConsole Class
this.publishers.push(new LogConsole());
// Create instance of `LogLocalStorage` Class
this.publishers.push(new `LogLocalStorage`());
}
You can now re-run the application and you should be logging to both the console window and to local storage. To test this, set a break point in the log()
method of the LogLocalStorage
class and see if it retrieves the previous values that you logged into local storage.
Log to Web API
The last logging class you are going to create is one to send an instance of the LogEntry
class to a Web API method. From this Web API you could then write code to store the log entry into a database table. I'm not going to provide you with a table - I'll leave that to you. I'm using Visual Studio and C# to create my Web API calls, so I'm going to add a C# class to my project that's the same name and has the same properties as the LogEntry
class I created in Angular.
public class LogEntry
{
public DateTime EntryDate { get; set; }
public string Message { get; set; }
public LogLevel Level { get; set; }
public object[] ExtraInfo { get; set; }
}
I'm also adding a C# enumeration to my project to map to the TypeScript LoggingLevel
enumeration.
public enum LogLevel
{
All = 0,
Debug = 1,
Info = 2,
Warn = 3,
Error = 4,
Fatal = 5,
Off = 6
}
Finally, I'm going to add a Web API controller class (Listing 7) to my project. This class has one method at this point to allow Angular to post the LogEntry
record to this method. It's in this Post()
method that you write the code to store the log data into a database table. For purposes of this article, I'm just going to return a result of OK (true
) back to the caller.
Listing 7: The LogController allows you to store log entries via a Web API call
public class LogController : ApiController
{
// POST api/<controller>
[HttpPost]
public IHttpActionResult Post([FromBody]LogEntry value)
{
IHttpActionResult ret;
// TODO: Write code to store logging data in a database table
// Return OK for now
ret = Ok(true);
return ret;
}
}
Now that you have the Web API classes created, you can go back to the log-publishers.ts
file and add some import statements for calling a Web API. Add the following import statements near the top of this file.
import { Http, Response, Headers, RequestOptions } from '@angular/http';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';
Add the LogWebApi
class shown in Listing 8 at the bottom of the log-publishers.ts
file. The constructor for this class is very similar to the one you wrote for the LogLocalStorage
class. You do need to include the HTTP service as you're going to need this to call the Web API. You also must call super()
to execute the constructor of the base class. Finally, set the location
property to the URL of the Web API call.
Listing 8: Create a LogWebApiService class to call a Web API for tracking log messages
export class `LogWebApi` extends LogPublisher {
constructor(private http: Http) {
// Must call `super()`from derived classes
super();
// Set location
this.location = "/api/log";
}
// Add log entry to back end data store
log(entry: LogEntry): Observable<boolean> {
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
return this.http.post(this.location, entry, options).map(response => response.json()).catch(this.handleErrors);
}
// Clear all log entries from local storage
clear(): Observable<boolean> {
// TODO: Call Web API to clear all values
return Observable.of(true);
}
private handleErrors(error: any): Observable<any> {
let errors: string[] = [];
let msg: string = "";
msg = "Status: " + error.status;
msg += " - Status Text: " + error.statusText;
if (error.json()) {
msg += " - Exception Message: " + error.json().exceptionMessage;
}
errors.push(msg);
console.error('An error occurred', errors);
return Observable.throw(errors);
}
}
The log()
method accepts a LogEntry
object that's sent to the Web API method. Because you're performing a POST
to the Web API, you need to create the appropriate headers to specify the content type you're sending as application/json
. The post()
method on the Angular HTTP service is called to pass the LogEntry
object to the Web API class you created.
You also need a clear()
method in this class to override the abstract method in the base class. In order to keep the length of this article shorter, I'm not showing how to do clear log entries, but it's similar to the log()
method. You call a Web API method that writes the appropriate SQL to delete all rows from your log table in your database.
Open the log-publishers.service.ts
file and modify the import statement to include your new LogWebApi
class.
import { LogPublisher, LogConsole, `LogLocalStorage`, LogWebApi } from "./log-publishers";
Open your log-publishers.service.ts
file and add an import for the HTTP service near the top of the file.
import { Http } from '@angular/http';
Next, add the HTTP service to the constructor of this class.
constructor(private http: Http) {
// Build publishers arrays
this.buildPublishers();
}
Finally, in the buildPublishers()
method, create a new instance of the LogWebApi
class and pass in the HTTP service as shown in the code below.
buildPublishers(): void {
// Create instance of LogConsole Class
this.publishers.push(new LogConsole());
// Create instance of `LogLocalStorage` Class
this.publishers.push(new `LogLocalStorage`());
// Create instance of `LogWebApi` Class
this.publishers.push(new LogWebApi(this.http));
}
You need to register the HTTP service with your AppModule
. Open app.module.ts
and add the following import near the top of this file.
import { HttpModule } from '@angular/http';
Add the HttpModule
to the imports property in the @NgModule()
function decorator.
imports: [BrowserModule, HttpModule],
Now that you have this new publisher added to the array, you should be able to run your logging application, and when you click on the Log Test button, you should see that it's making the call to your Web API method.
Read Publishers from JSON File
Open the log-publishers.ts
file and add a new class called LogPublisherConfig
. This class is going to hold the individual objects read from a JSON file you see in Listing 9.
class LogPublisherConfig {
loggerName: string;
loggerLocation: string;
isActive: boolean;
}
Build the JSON file by adding an \assets
folder underneath the \src\app folder
. Add a JSON file called log-publishers.json
in the \assets
folder and add the code from Listing 9. Each of the object literals in this JSON array relate to one of the classes you created for logging to the console, local storage, and the Web API.
Listing 9: Create a LogLocalStorage Service class to store messages into local storage
[
{
"loggerName": "console",
"loggerLocation": "",
"isActive": true
},
{
"loggerName": "localstorage",
"loggerLocation": "logging",
"isActive": true
},
{
"loggerName": "webapi",
"loggerLocation": "/api/log",
"isActive": true
}
]
Add a few more import statements to your log-publishers.service.ts
file.
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';
Add a constant just after these imports, to point to this file.
const PUBLISHERS_FILE = "/src/app/assets/log-publishers.json";
In the LogPublishersService
class, add a new method named handleErrors (Listing 10) to take care of any errors that might happen during any HTTP service calls. Also, in the LogPublishersService
class, create a new method to read the data from this JSON file. Let's call this method getLoggers()
. Because you already injected the HTTP service into this class, you can use this service to read from the JSON file.
getLoggers(): Observable<LogPublisherConfig[]> {
return this.http.get(PUBLISHERS_FILE).map(response => response.json()).catch(this.handleErrors);
}
Listing 10: You always should have a method to handle errors when using the HTTP service
private handleErrors(error: any):
Observable<any> {
let errors: string[] = [];
let msg: string = "";
msg = "Status: " + error.status;
msg += " - Status Text: " + error.statusText;
if (error.json()) {
msg += " - Exception Message: " + error.json().exceptionMessage;
}
errors.push(msg);
console.error('An error occurred', errors);
return Observable.throw(errors);
}
Now that you have this method to return the array from this file, modify the buildPublishers()
method to subscribe to this Observable array of LogPublisherConfig
object. The new code for the buildPublishers()
method is shown in Listing 11.
Listing 11: Create your array of publishers by reading the values from a JSON file
buildPublishers(): void {
let logPub: LogPublisher;
this.getLoggers().subscribe(response => {
for (let pub of response.filter(p => p.isActive)) {
switch (pub.loggerName.toLowerCase()) {
case "console":
logPub = new LogConsole();
break;
case "localstorage":
logPub = new `LogLocalStorage`();
break;
case "webapi":
logPub = new LogWebApi(this.http);
break;
}
// Set location of logging
logPub.location = pub.loggerLocation;
// Add publisher to array
this.publishers.push(logPub);
}
});
}
The buildPublishers()
method calls the getLoggers()
method and subscribes to the output from this method. The output is an array of LogPublisherConfig
objects. The array of configuration objects is filtered to only loop through those that have their isActive
property set to a True
value. For each iteration through the loop, check the loggerName
property and compare that value with those listed in each case statement. If a match is found, a new instance of the corresponding LogPublisher
class is created. The loggerLocation
property is set to the location property of each LogPublisher
class. The newly instantiated publisher object is then added to the publishers
array property. As all of this happens in the constructor of this service class; the publishers array is already set to the list of publishers to use by the time it is injected into the LogService
class. You should be able to run the Angular application and click on the Log Test button and see the log messages published to all publishers marked as isActive
in the JSON file.
Summary
It's always a best practice to log messages as you move throughout your Angular applications. Exceptions should always be logged, but you may also wish to log the debug, warning, and informational messages as well. Creating a flexible logging system like the one presented in this article assists with this best practice. Using a configuration JSON file to store which publishers you wish to log to saves having to hard-code publishers within your log service class.