Nikolay Grozev

# Introduction

Recently, I have been working on a few TypeScript projects where I had to implement audit logging for many opeartions. For example, I had to log every request to an external API and the received response or error message. I also had to audit log every attempt to access externally hosted resources (e.g. SMTP or WebDAV servers).

I had to wrap every function invocation, which needed to be audited, with the same logging logic. For simplicity, let’s assume the audit logging had to be done via console.log. For just 2 API calls I had to write code like this:

# Usage

Let’s demonstrate how to use auditWrap in practice to audit log a couple of API Calls:

 1 2 3 4 5 6 7 8 import axios from 'axios'; async function example() { const call1Result = await auditWrap(axios.get)('https://jsonplaceholder.typicode.com/todos/1'); const call2Result = await auditWrap(axios.get)('https://jsonplaceholder.typicode.com/todos/2'); } example()

The produced audit log is huge - every axios parameter and response element is printed out. Below is a subset of the output (... denotes omited output):

 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 Attempting to call function: "wrap" with arguements: "https://jsonplaceholder.typicode.com/todos/1" Call to function "wrap" suceeded with result: { "status": 200, "statusText": "OK", "headers": {...}, "config": {...}, "request": {...}, "data": { "userId": 1, "id": 1, "title": "delectus aut autem", "completed": false } } Attempting to call function: "wrap" with arguements: "https://jsonplaceholder.typicode.com/todos/2" Call to function "wrap" suceeded with result: { "status": 200, "statusText": "OK", "headers": {...}, "config": {...}, "request": {...}, "data": { "userId": 1, "id": 2, "title": "quis ut nam facilis et officia qui", "completed": false } }

Notice that name of the function in the audit log is wrap. Indeed, if you print axios.get.name, you’ll see that this is its actual function name.

Nevertheless, the above aproach is a significant improvement. We managed to eliminate all try-catch-log boilerplate code and to preserve type safety. However, we can do a bit better. In the above example, we generated a huge amount of log. What if we want to audit log only specific parts of the output or have more readable function names?

We can achieve this by introducing higher level function(s), which hide the details of the underlying libraries and return exactly what we need:

 1 2 3 4 5 6 7 8 const apiCall = (url: string, config?: AxiosRequestConfig) => axios.get(url, config).then(d => d.data) async function example() { const call1Result = await auditWrap(apiCall)('https://jsonplaceholder.typicode.com/todos/1'); const call2Result = await auditWrap(apiCall)('https://jsonplaceholder.typicode.com/todos/2'); } example()

The output of the above is:

 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Attempting to call function: "apiCall" with arguements: "https://jsonplaceholder.typicode.com/todos/1" Call to function "apiCall" suceeded with result: { "userId": 1, "id": 1, "title": "delectus aut autem", "completed": false } Attempting to call function: "apiCall" with arguements: "https://jsonplaceholder.typicode.com/todos/2" Call to function "apiCall" suceeded with result: { "userId": 1, "id": 2, "title": "quis ut nam facilis et officia qui", "completed": false }