Merge pull request #3 from yedidyas/transation-id-new-changes

transaction id new changes
This commit is contained in:
Yedidya Schwartz
2021-02-05 11:37:41 +02:00
committed by GitHub
5 changed files with 78 additions and 48 deletions

View File

@ -47,7 +47,7 @@ Read in a different language: [![CN](/assets/flags/CN.png)**CN**](/README.chines
## Table of Contents
1. [Project Structure Practices (5)](#1-project-structure-practices)
2. [Error Handling Practices (13) ](#2-error-handling-practices)
2. [Error Handling Practices (12) ](#2-error-handling-practices)
3. [Code Style Practices (12) ](#3-code-style-practices)
4. [Testing And Overall Quality Practices (13) ](#4-testing-and-overall-quality-practices)
5. [Going To Production Practices (19) ](#5-going-to-production-practices)
@ -235,16 +235,6 @@ especially if the cause of the abnormal behavior is inside of the missing functi
🔗 [**Read More: returning promises**](/sections/errorhandling/returningpromises.md)
<br/><br/>
## ![✔] 2.13 Give your error's log a context by adding a correlation ID
**TL;DR:** Correlation ID lets you link log records, even if they belong to different services. It can save your day when a process including 20 different microservices throws an exception in one of them, and you have no idea where did the problem start across the flow.
**Otherwise:** Once an error will occure, you might read the logs without any context of understanding what caused the unexpected input and which logs of other services are related to your investigated issue.
🔗 [**Read More: Correlation ID: help your logs tell you a story and give your error a context**](/sections/errorhandling/correlationid.md)
<br/><br/><br/>
<p align="right"><a href="#table-of-contents">⬆ Return to top</a></p>

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

View File

@ -1,36 +0,0 @@
# Correlation ID: help your logs tell you a story and give your error a context
### One Paragraph Explainer
Correlation ID is one of the best problem-solving patterns. It lets you link log records, even if they belong to different async functions, or even different services. If your process includes calls of 20 async functions, adding a correlaction ID to your logs is a must. This may be your only way to tell that a specific entry from the controller layer belongs to the same request as that entry from the DAL layer.
Moreover, if your system consumes other services and is itself a producer service, this practice can help you. By adding a correlation ID, your transaction logs can become into a story that tells itself by filtering your logs with a specific correlation ID, instead of try linking the cross-services transaction logs to each other by yourself. It can save your day when a process including several different microservices throws an exception in one of them, and you have no idea where did the problem start across the flow.
<br/>
### Code Example: passing the correlation ID between services on the requet http context
Here is an example of using [express-http-context](https://www.npmjs.com/package/express-http-context) library as part of a middleware to set the forwarded correlation ID on the http context after it was extracted from the request.
If the correlation ID doesn't exist on the request, since this may be the first request in the requests chain, it's being generated.
```javascript
const httpContext = require('express-http-context');
app.use((req, res, next) => {
// Extract the correlation ID from the previous request, or generate it if this is the first request in the transaction
const correlationId = req.get('X-Correlation-ID') || uuid.v4();
// Set the correaltion ID on the http context
httpContext.set('correlationId', correlationId);
// Set the correaltion ID on the response
res.set('X-Correlation-ID', correlationId);
next();
});
```
<br/>
### Blog Quote: "The notion of a Correlation ID is simple. Its a value that is common to all requests, messages and responses in a given transaction. With this simplicity you get a lot of power."
From [rapid7](https://blog.rapid7.com/2016/12/23/the-value-of-correlation-ids/)
> When you can group a transactions events under a unifying value, the Correlation ID, you can spend your time fixing the problem rather than finding the problem. Without a Correlation ID, well… there is a saying, “if you put a chimpanzee in front of typewriter for eternity, eventually the animal will type out Hamlet.” The same could be said for logging without a Correlation ID, except none of us will be here for eternity. Use a Correlation ID. You will be glad you did.

View File

@ -8,7 +8,65 @@ A typical log is a warehouse of entries from all components and requests. Upon d
<br/><br/>
### Code example: typical Express configuration
### Code example: sharing TransactionId among current request functions
using [cls-rtracer](https://www.npmjs.com/package/cls-rtracer) (a library based on [async-local-storage](https://nodejs.org/api/async_hooks.html#async_hooks_class_asynclocalstorage), implemented for Express & Koa middlewares and Fastify & Hapi plugins)
```javascript
const express = require('express');
const rTracer = require('cls-rtracer');
const app = express();
app.use(rTracer.expressMiddleware());
app.get('/getUserData/{id}', async (req, res, next) => {
try {
const user = async usersRepo.find(req.params.id);
// At any point in the app after cls-rtracer middleware was initialized, even when 'req' object doesn't exist, the TransactionId is reachable
const transactionId = rTracer.id();
logger.info(`user ${user.id} data was fetched successfully`, { transactionId });
res.json(user);
} catch (err) {
// If not for the example, I'd recommand using rTracer.id() from inside the logger component, to prevent from using it all over the code
logger.error(`error while fetching user id ${req.params.id} data`, { transactionId: rTracer.id(), error: err });
next(err);
}
})
```
<br/><br/>
### Code example: sharing TransactionId among microservices
```javascript
// cls-tracer has the ability to store the TransactionId on your service outgoing requests headers, and extract the TransactionId from incoming requests headers, just by overriding the default middleware config
app.use(rTracer.expressMiddleware({
// Add TransactionId to the header
echoHeader: true,
// Respect TransactionId from header
useHeader: true,
// TransactionId header name
headerName: 'x-transaction-id'
}));
const axios = require("axios");
// Now, the external service will automaticlly get the current TransactionId as header
const response = await axios.get('https://externalService.com/api/getAllUsers');
```
<br/><br/>
There are two restrictions on using cls-rtracer, due to its dependence on async-local-storage (You can think of async-local-storage as the node.js alternative to thread local storage, more details [here](https://www.freecodecamp.org/news/async-local-storage-nodejs/)):
1. async-local-storage requires Node v.14.
2. async-local-storage is based on a lower level construct in Node called async_hooks which is still experimental, so you may have the fear of performance problems. Even if they do exist, they are very negligible, but you should make your own considerations.
<br/>
<details>
<summary><strong>Code example - typical Express configuration without async-local-storage dependence</strong></summary>
```javascript
// when receiving a new request, start a new isolated context and set a transaction id. The following example is using the npm library continuation-local-storage to isolate requests
@ -37,3 +95,21 @@ class logger {
}
}
```
</details>
<br/><br/>
### Good: Logs with an assigned TransactionId - can be used as filter to see only a single flow
![alt text](https://i.ibb.co/YjJwgbN/logs-with-transaction-id.jpg "Logs with transaction id")
<br/><br/>
### Bad: logs without a TransactionId - no option to use a filter and see only a single flow, you need to understand by yourself which logs are relevant between all the "noise" around
![alt text](https://i.ibb.co/PFgVNfn/logs-withtout-transaction-id.jpg "Logs with transaction id")
<br/><br/>
### Blog Quote: "The notion of a Correlation ID is simple. Its a value that is common to all requests, messages and responses in a given transaction. With this simplicity you get a lot of power."
From [rapid7](https://blog.rapid7.com/2016/12/23/the-value-of-correlation-ids/)
> In the old days when transactional behavior happened in a single domain, in step-by-step procedures, keeping track of request/response behavior was a simple undertaking. However, today one request to a particular domain can involve a myriad of subsequent asynchronous requests from the starting domain to others. For example, you send a request to Expedia, but behind the scenes Expedia is forwarding your request as a message to a message broker. Then that message is consumed by a hotel, airline and car rental agency that responds asynchronously too. So the question comes up, with your one request being passed about to a multitude of processing consumers, how do we keep track of the transaction? The answer is: use a Correlation ID.