Vjerci.com

A personal blog by software engineer

January, 7-th, 2026

Clean Architecture -> Tracing



This tutorial series assumes you are familiar with golang, as we will be using it thorough this article series.


About traceability

What is traceability

Traceability is ability to connect what is going on with single request throughout its lifecycle. It is usually done by assigning an unique id (uuid) to request on the frontend or at the api gateway. We usually assign the uuid to context and whenever we log some info we also enrich the log with uuid.

some basic code would be

// user/handler.go
func UserHandler(ctx echo.Context) {
	enrichedContext = ctx.WithValue(ctx, "uuid", uuid.New())
	logging.Info(enrichedContext, "handling user creation")
	service.CreateUser(enrichedContext)
}

// user/service.go
func CreateUser(ctx echo.Context){
	logging.Info(ctx, "creating user")
}

If request does some kind of forking or starts multiple jobs original request id is usually passed around and each job usually enriches the context with it’s own uuid.

Where is traceability useful:

Usually in bigger companies you will get a ticket saying this request with a certain uuid went wrong.

Here is were traceability becomes useful. If you have a proper logging in place you should be able to go into something like grafana cloud, check the logs and see what request was doing. Without proper logging and traceability in place, you would need to wonder which code path it went. Lets say you have just an error log “failed to save user”.

In example below, you would have to wonder and test was it CompanyHandler that was invoker or was it UserHandler that was invoked.

// company/handler.go
func CompanyHandler(ctx echo.Context) {
	// do some company creation logic
	userService.CreateUser(enrichedContext)
}

// user/handler.go
func UserHandler(ctx echo.Context) {
	// do some user creation logic
	service.CreateUser(enrichedContext)
}

// user/service.go
func CreateUser(ctx echo.Context){
	logging.Info(ctx, "creating user")
	err := repository.CreateUser(ctx)
	if err != nil {
		logging.Info(ctx, "failed to save user")
	}
}

While if you have proper logging in place, tied together with enriching the context you don’t have to wonder and don’t have to test once things go wrong.

Here is a good example:

// company/handler.go
func CompanyHandler(ctx echo.Context) {
	enrichedContext = ctx.WithValue(ctx, "uuid", uuid.New())
	logging.Info(enrichedContext, "handling company creation")
	userService.CreateUser(enrichedContext)
}

// user/handler.go
func UserHandler(ctx echo.Context) {
	enrichedContext = ctx.WithValue(ctx, "uuid", uuid.New())
	logging.Info(enrichedContext, "handling user creation")
	service.CreateUser(enrichedContext)
}

// user/service.go
func CreateUser(ctx echo.Context){
	logging.Info(ctx, "creating user")
	err := repository.CreateUser(ctx)
	if err != nil {
		logging.Info(ctx, "failed to save user")
	}
}

With the example above you should be able to easily track the logs and get lines such as this

{"uuid":"a682c97b-4fd5-4710-b506-38497c950d7b", "message": "handling company creation"}
{"uuid":"a682c97b-4fd5-4710-b506-38497c950d7b", "message": "failed to save user"}

In proper systems it is usually better if you have some form of logging and traceability to tell you which code path request went. It makes debugging a whole lot easier. There is no need for additional debug logging, no need for breakpoints.

You simply know which code path the request went all by searching for request uuid in your observability tool.

When to use it

Some general rule is: if you have 2 or more code paths going down the same decently sized code path it might be a good idea to add some logging around it.

If you are having a decently sized code path, such as in example above… where you could create a user either through creation of company or trough registration of new user… adding some logging should prove useful.

Of course if you are using some kind of widely used utility function, you usually don’t want to add logging there, as it would pollute the logs.

Request Identification

As i’ve mentioned, usually adding information… such as request uuid, is not done in the handler. It is either done in middleware or passed down trough frontend or on api gateway or whatever you are using. It is usually done at the start of the request or at the start of user interaction.

It isn’t uncommon to log both the session and each interaction/request.

In my opinion it is a matter of philosophy if request starts at the frontend with user interaction or on the backend. However i must admit, in practice, tying those 2 together can be quite useful.


Summary

We’ve learned what basics of tracing is, where it becomes useful. We’ve learned most common way of enriching the context and we’ve learned a little bit about online tools which can help us debug bad requests.