I started working with Angular 2 a little while back and was sad to learn that my favorite all-time service, $log, wasn’t part of the framework. I started using console.debug/log/warn/error instead. For you non-Angular folk, $log just wraps around the console.log family of functions and adds, among other things, a global mechanism for turning off debug messages without having to delete the actual logging from your code.

I’ve started to put together a simple logging facility in TypeScript replicate it. Since TS is growing in popularity I also thought I’d share some of the concepts that underlie it.

The logger function is comprised of two primary items:

An interface that defines the logger’s minimum required functionality. A class that implements that interface.

Here is the interface:

Logger interface

This interface specifies four public methods (debug, warn, etc.). None of these return anything (hence “void”) and they share a common signature:

primaryMessage: Some string of text.

supportData: a variable number of any’s.

This example shows two TypeScript features

Interfaces: In this case, an interface defines required functionality. You’ll see the mechanism for this shortly. Rest operators. The “…supportingData” is a catch-all for “all remaining parameters”. You can pass any number of parameters and they will all be stuffed into this any[] array.

When implemented by a class, I will be able to do something like:

myLogger.debug(“My important vars at this time are:”, var1, var2);

Interfaces do nothing on their own. They only describe structure and required methods. A class is said to implement the interface when it meets the requirements. Here’s an example:

Implementing LogInterface

The above example defines a class that includes four public methods (debug, info, etc.). It also defines one private method, emitLogMessage. We know it must implement LogInterface because the class declaration explicitly says so:

export class Log implements LogInterface {

Each of the methods delegates the actual log activity to to the private method, emitLogMessage(). Keep in mind a few things here:

Interfaces never define private methods.

Interfaces don’t constrain what you can define. You can define additional public methods, private methods, static methods — pretty much anything you want.

In other words, interfaces define minimum required functionality.

Interfaces are great for other things too, but those are beyond the scope of this little example.

As I wrote above, these methods each delegate the actual work of logging out messages to the console to the emitLogMessage() method:

private emitLogMessage(msgType: “debug” | “info” | “warn” | “error”, msg: string, supportingDetails: any[]) {

(Note that this is a very naive way of implementing logging — not all browsers and browser versions will support this approach to invoking console() so don’t use this without proper testing).

This shows us another interesting and useful TypeScript feature, “union types”. The signature’s msgType parameter is a brand new type of type which can only take one of the the literal values “debug, “info”, “warn” or “error”. By defining it this way, we’re telling the compiler that msgType must be passed one of these four values. Otherwise, it’s an error. Better yet, common IDEs such as Visual Studio and Visual Studio code will provide on-the-spot intellisense telling you this fact.

Finally, to use it, I instantiate the Log and use the methods. Here’s an example:

Hello? Hello?

In Angular, we actually use the framework’s dependency injection process for this rather than instantiating the object this way, but the result is the same for the purposes of this article.

You might be wondering why we’re using an interface here at all. The answer to that question largely comes out of the SOLID design principles (read here for a light introduction). Namely that you should program to interfaces, not implementations. It pays off as complexity increases. But let’s make that a little more concrete. Suppose that we want to log API requests to some kind of API logging mechanism and that in order to do that, we actually need to connect to a REST endpoint. Simple! We just build out a new class that implements the LoggerInterface. This new class defines debug/info/warn/error methods but instead of writing out to the web browser console, they invoke API calls. Now we have two logging options that work identically as far as the client is concerned — myLogger.debug(...), myLogger.error(…) — but whose actual implementations are quite different from each other.

Here’s another example: suppose you’ve built out a fairly complex solution and decorated it with a lot info/debug, etc. messages. You notice some performance problems but you’re really not sure the precise location of the bottleneck. You could write a new implementation of the logger interface that tracks the amount if time elapsed between log calls and use that help isolate the problem.

In conclusion, interfaces, rest parameters and union types are very useful language features. Interfaces let us adhere more directly to tried and true design principles. Rest parameters give us a lot of flexibility on method signature when we need it. They are very useful for Reac. Union types help define a universe of valid parameter values. All good stuff.

Hope this helps someone learning about TypeScript!

</end>