Go errors are a story, most teams lose the plot

seeb1 pts0 comments

Go errors are a story, most teams lose the plot · Robin Siep&darr;Skip to main content<br>Logs are the primary way to understand what your system is doing, and error logs most of all. But your logs can only surface what your errors carry. Due to a fundamental difference in how Go treats errors compared to most other ecosystems, the ones I see from clients&rsquo; Go applications consistently carry less than they should.<br>The case for logical traces #<br>Unlike many other languages, errors in Go do not contain stack traces. Instead, errors are typically wrapped with context as they propagate up through the system.<br>func greetUser(id int64) error {<br>user, err := getUser(id)<br>if err != nil {<br>return fmt.Errorf("unable to get user: %w", err)<br>fmt.Printf("Hi %s", user.name)<br>return nil<br>The error produced by the example above might look like this:<br>unable to get user: failed to query users table: connection refused<br>Compared to the stack trace that might get produced by a Java application with the same failure:<br>java.sql.SQLException: Connection refused: connect<br>at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:129)<br>at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97)<br>at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:825)<br>at com.mysql.cj.jdbc.ConnectionImpl.(ConnectionImpl.java:448)<br>at com.mysql.cj.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:241)<br>at com.mysql.cj.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:198)<br>at java.sql/java.sql.DriverManager.getConnection(DriverManager.java:677)<br>at java.sql/java.sql.DriverManager.getConnection(DriverManager.java:189)<br>at com.example.db.ConnectionPool.acquire(ConnectionPool.java:87)<br>at com.example.repository.UserRepository.findById(UserRepository.java:42)<br>at com.example.service.UserService.getUser(UserService.java:28)<br>at com.example.handler.GreetingHandler.greetUser(GreetingHandler.java:19)<br>at com.example.handler.GreetingHandler$$FastClassBySpringCGLIB$$abc123.invoke()<br>at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)<br>at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)<br>at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)<br>at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)<br>... 47 more<br>Our Go example essentially builds a logical trace. When maintained well, it has two advantages over a traditional stack trace: it&rsquo;s human-readable and it&rsquo;s complete even across channels, goroutines, and other handoffs that a stack trace might lose.<br>Gaps in your story #<br>Maintaining this context, however, takes constant discipline from your team. Any lapse may make it difficult, if not impossible, to trace errors that surface. Take the error from the previous section: connection refused is all that&rsquo;d be logged if the context had not been added.<br>And, like comments, the context may become outdated and misleading as your application develops. A wrap message written two years ago may no longer capture enough detail to figure out why the underlying error occurred.<br>In practice, most teams I work with fail to keep up with this maintenance burden (or even realise it exists). As a result, the errors their software emits in production do not contain sufficient information to resolve their cause or sometimes even determine what went wrong.<br>Traditional stack traces do not suffer from this. They do not need to be kept up to date, and for the thread where the error occurred, they&rsquo;re guaranteed to be complete and exact.<br>The right approach for most teams #<br>Logical traces and stack traces serve the same purpose: attaching context to errors so they can be understood later. Logical traces give you richer context but only when there are no gaps, making them expensive. Stack traces, on the other hand, give you cheaper context automatically and reliably.<br>Although stack traces are not added to errors in Go by default, they&rsquo;re quite simple to add yourself. This allows us to choose between the two alternatives. However, the question isn&rsquo;t actually which to use, it&rsquo;s how much of the expensive, context-rich kind your team can sustain. This leads me to advocate a middle-ground approach for most teams:<br>By adding stack traces whenever your application creates an error or first encounters one made by a third-party you ensure all errors in your Go application contain a stack trace with the least amount of work and cognitive load. Additional context can be added throughout when desired by the programmer, but is no longer crucial. The stack trace will always be there to fall back on.<br>Returning to our earlier example: a bare connection refused error is perfectly debuggable if it carries a stack trace pointing through getUser and greetUser. Developers can still add context like failed to query users table: connection...

java stack errors context traces error

Related Articles