<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged logging</title>
<link>https://coderlegion.com/tag/logging</link>
<description>Powered by Question2Answer</description>
<item>
<title>How to Add Logging Without Cluttering Your Code</title>
<link>https://coderlegion.com/25521/how-to-add-logging-without-cluttering-your-code</link>
<description>&lt;p&gt;In one of my Python projects, I needed to create fairly detailed logs describing the program's execution. Most of the project is &lt;em&gt;glue code&lt;/em&gt; that uses numerous external services and automates processes that were previously performed manually. I do not have full control over them, and my knowledge of how they work is very limited. Because different processes often use the same services, I wrapped those services in classes. The code implementing one of these automations looks roughly like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from clients.invoices import InvoiceClient
from clients.orders import OrderClient
from clients.partner import PartnerClient


def export_order(
    order_id: str,
    orders: OrderClient,
    invoices: InvoiceClient,
    partner: PartnerClient,
) -&amp;gt; None:
    order = orders.get_order(order_id)
    attachments = orders.get_attachments(order_id)

    if order.requires_invoice:
        invoice = invoices.create(
            order_id=order_id,
            positions=order.costs,
            payment_term=14,
        )
        attachments.append(invoice)

    partner.send_order(order, attachments)
    orders.set_status(order_id, &quot;exported&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At first, I added logging calls directly to the code, like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def export_order(
    order_id: str,
    orders: OrderClient,
    invoices: InvoiceClient,
    partner: PartnerClient,
) -&amp;gt; None:
    logger.info(f&quot;Exporting order {order_id}&quot;)
    order = orders.get_order(order_id)
    logger.info(f&quot;Got order {order_id}: {order}&quot;)

    logger.info(f&quot;Getting attachments for order {order_id}&quot;)
    attachments = orders.get_attachments(order_id)

    if order.requires_invoice:
        logger.info(f&quot;Creating invoice for order {order_id}&quot;)
        invoice = invoices.create(
            order_id=order_id,
            positions=order.costs,
            payment_term=14,
        )
        logger.info(f&quot;Created invoice for order {order_id}: {invoice}&quot;)
        attachments.append(invoice)

    logger.info(f&quot;Sending order {order_id} to partner&quot;)
    partner.send_order(order, attachments)
    logger.info(f&quot;Setting status of order {order_id} to 'exported'&quot;)
    orders.set_status(order_id, &quot;exported&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is easy to see how cluttered the code has become. Add dozens of similar functions throughout the project, and the code becomes difficult to read and maintain.&lt;/p&gt;
&lt;p&gt;My first step toward cleaning it up was to extract the repeated pattern into a separate context manager. After all, every stage of the process looked similar: record when it starts, perform the operation, and then record either its success or failure.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from collections.abc import Iterator
from contextlib import contextmanager


@contextmanager
def log_step(message: str) -&amp;gt; Iterator[None]:
    try:
        logger.info(f&quot;Starting: {message}&quot;)
        yield
    except Exception:
        logger.exception(f&quot;Error during: {message}&quot;)
        raise
    else:
        logger.info(f&quot;Finished: {message}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Everything before &lt;code&gt;yield&lt;/code&gt; runs when execution enters the &lt;code&gt;with&lt;/code&gt; block. If the block completes successfully, the &lt;code&gt;else&lt;/code&gt; branch runs. If an exception occurs, the context manager records the failure and raises the exception again, so logging does not change the application's behavior.&lt;/p&gt;
&lt;p&gt;I could now write the automation code like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def export_order(
    order_id: str,
    orders: OrderClient,
    invoices: InvoiceClient,
    partner: PartnerClient,
) -&amp;gt; None:
    with log_step(f&quot;Getting order {order_id}&quot;):
        order = orders.get_order(order_id)
        attachments = orders.get_attachments(order_id)

    if order.requires_invoice:
        with log_step(f&quot;Creating invoice for order {order_id}&quot;):
            invoice = invoices.create(
                order_id=order_id,
                positions=order.costs,
                payment_term=14,
            )
            attachments.append(invoice)

    with log_step(f&quot;Sending order {order_id} to partner&quot;):
        partner.send_order(order, attachments)

    with log_step(f&quot;Setting status of order {order_id} to 'exported'&quot;):
        orders.set_status(order_id, &quot;exported&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This way, I still get an entry for the start, completion, or failure of every stage, but the business logic is no longer interleaved with pairs of nearly identical &lt;code&gt;logger.info&lt;/code&gt; calls. The context manager also ensures that every event is recorded in a consistent format.&lt;/p&gt;
&lt;p&gt;There is admittedly a little less code, but the result still does not look ideal. In more complex processes, successive &lt;code&gt;with&lt;/code&gt; blocks introduce many additional levels of indentation, which can be cumbersome and significantly reduce readability.&lt;/p&gt;
&lt;p&gt;At this point, I decided to use decorators for logging and apply them to methods of classes responsible for communicating with external services. The decorator did not need to implement the entire mechanism again—it only needed to execute the function inside the context manager I had already prepared:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from functools import wraps
from typing import Callable, ParamSpec, TypeVar


P = ParamSpec(&quot;P&quot;)
R = TypeVar(&quot;R&quot;)


def log_function(message: str):
    def decorator(func: Callable[P, R]) -&amp;gt; Callable[P, R]:
        @wraps(func)
        def wrapper(*args: P.args, **kwargs: P.kwargs) -&amp;gt; R:
            with log_step(message):
                return func(*args, **kwargs)

        return wrapper

    return decorator
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This decorator also works with instance methods. The wrapped function remains a descriptor, so Python automatically passes the instance as the first argument, &lt;code&gt;self&lt;/code&gt;. Meanwhile, &lt;code&gt;@wraps&lt;/code&gt; preserves the original method's name, documentation, and other metadata.&lt;/p&gt;
&lt;p&gt;I could therefore move responsibility for logging into the client classes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class OrderClient:
    @log_function(&quot;Getting order&quot;)
    def get_order(self, order_id: str) -&amp;gt; Order:
        return self._api.get_order(order_id)

    @log_function(&quot;Getting order attachments&quot;)
    def get_attachments(self, order_id: str) -&amp;gt; list[Attachment]:
        return self._api.get_attachments(order_id)

    @log_function(&quot;Setting order status&quot;)
    def set_status(self, order_id: str, status: str) -&amp;gt; None:
        self._api.set_status(order_id, status)


class InvoiceClient:
    @log_function(&quot;Creating invoice&quot;)
    def create(
        self,
        order_id: str,
        positions: list[Cost],
        payment_term: int,
    ) -&amp;gt; Invoice:
        return self._api.create_invoice(
            order_id=order_id,
            positions=positions,
            payment_term=payment_term,
        )


class PartnerClient:
    @log_function(&quot;Sending order to partner&quot;)
    def send_order(
        self,
        order: Order,
        attachments: list[Attachment],
    ) -&amp;gt; None:
        self._api.send_order(order, attachments)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As a result, the process code returned almost to its original form:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def export_order(
    order_id: str,
    orders: OrderClient,
    invoices: InvoiceClient,
    partner: PartnerClient,
) -&amp;gt; None:
    order = orders.get_order(order_id)
    attachments = orders.get_attachments(order_id)

    if order.requires_invoice:
        invoice = invoices.create(
            order_id=order_id,
            positions=order.costs,
            payment_term=14,
        )
        attachments.append(invoice)

    partner.send_order(order, attachments)
    orders.set_status(order_id, &quot;exported&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Logging now lives at the boundary where the application communicates with external systems, while the function describing the process once again focuses on its logic. Every call to a decorated method still generates an entry for its start, completion, or failure—regardless of how many automations use a given client.&lt;/p&gt;
&lt;p&gt;This solution has an important limitation, however: the message passed to the decorator is static. Every order therefore produces exactly the same &lt;code&gt;Getting order&lt;/code&gt; entry. While defining the class, I cannot use &lt;code&gt;f&quot;Getting order {order_id}&quot;&lt;/code&gt; because the value of &lt;code&gt;order_id&lt;/code&gt; does not exist until the method is called.&lt;/p&gt;
&lt;p&gt;I wanted to retain the decorator's simple syntax while allowing the message to refer to function arguments:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class OrderClient:
    @log_function(&quot;Getting order {order_id}&quot;)
    def get_order(self, order_id: str) -&amp;gt; Order:
        return self._api.get_order(order_id)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Simply calling &lt;code&gt;message.format(**kwargs)&lt;/code&gt; is not enough. After all, the method can be called as either &lt;code&gt;get_order(order_id=&quot;123&quot;)&lt;/code&gt; or &lt;code&gt;get_order(&quot;123&quot;)&lt;/code&gt;. In the second case, the &lt;code&gt;kwargs&lt;/code&gt; dictionary is empty, while &lt;code&gt;args&lt;/code&gt; contains the client instance and the order identifier.&lt;/p&gt;
&lt;p&gt;Python provides the appropriate tools for this in the &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.python.org/3/library/inspect.html#introspecting-callables-with-the-signature-object&quot;&gt;&lt;code&gt;inspect&lt;/code&gt;&lt;/a&gt; module. The &lt;code&gt;signature()&lt;/code&gt; function reads the decorated function's signature, while &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.python.org/3/library/inspect.html#inspect.Signature.bind&quot;&gt;&lt;code&gt;Signature.bind()&lt;/code&gt;&lt;/a&gt; maps the supplied positional and keyword arguments to its parameter names. The result is a &lt;code&gt;BoundArguments&lt;/code&gt; object containing a ready-to-use mapping such as &lt;code&gt;{&quot;self&quot;: client, &quot;order_id&quot;: &quot;123&quot;}&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The extended version of the decorator looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from functools import wraps
from inspect import signature
from typing import Callable, ParamSpec, TypeVar


P = ParamSpec(&quot;P&quot;)
R = TypeVar(&quot;R&quot;)


def log_function(
    message_template: str,
) -&amp;gt; Callable[[Callable[P, R]], Callable[P, R]]:
    def decorator(func: Callable[P, R]) -&amp;gt; Callable[P, R]:
        func_signature = signature(func)

        @wraps(func)
        def wrapper(*args: P.args, **kwargs: P.kwargs) -&amp;gt; R:
            bound_args = func_signature.bind(*args, **kwargs)
            bound_args.apply_defaults()
            log_context = {
                name: value
                for name, value in bound_args.arguments.items()
                if name not in {&quot;self&quot;, &quot;cls&quot;}
            }
            message = message_template.format_map(log_context)

            with log_step(message):
                return func(*args, **kwargs)

        return wrapper

    return decorator
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The signature is read only once, when the function is decorated. On every call, &lt;code&gt;bind()&lt;/code&gt; builds a mapping of the supplied values, and &lt;code&gt;apply_defaults()&lt;/code&gt; fills in the default values of omitted arguments. I remove &lt;code&gt;self&lt;/code&gt; and &lt;code&gt;cls&lt;/code&gt; from that mapping to prevent the representation of an entire instance from accidentally ending up in the message. Finally, &lt;code&gt;format_map()&lt;/code&gt; substitutes the selected values into the template.&lt;/p&gt;
&lt;p&gt;The decorators can now use method arguments regardless of how those arguments are passed:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class OrderClient:
    @log_function(&quot;Getting order {order_id}&quot;)
    def get_order(self, order_id: str) -&amp;gt; Order:
        return self._api.get_order(order_id)

    @log_function(&quot;Getting attachments for order {order_id}&quot;)
    def get_attachments(self, order_id: str) -&amp;gt; list[Attachment]:
        return self._api.get_attachments(order_id)

    @log_function(&quot;Setting order {order_id} status to {status}&quot;)
    def set_status(self, order_id: str, status: str) -&amp;gt; None:
        self._api.set_status(order_id, status)


class InvoiceClient:
    @log_function(
        &quot;Creating invoice for order {order_id} &quot;
        &quot;with a {payment_term}-day payment term&quot;
    )
    def create(
        self,
        order_id: str,
        positions: list[Cost],
        payment_term: int,
    ) -&amp;gt; Invoice:
        return self._api.create_invoice(
            order_id=order_id,
            positions=positions,
            payment_term=payment_term,
        )


class PartnerClient:
    @log_function(&quot;Sending order {order.id} to partner&quot;)
    def send_order(
        self,
        order: Order,
        attachments: list[Attachment],
    ) -&amp;gt; None:
        self._api.send_order(order, attachments)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.python.org/3/library/string.html#format-string-syntax&quot;&gt;Format string syntax&lt;/a&gt; supports references not only to arguments themselves but also to their attributes. Therefore, &lt;code&gt;{order.id}&lt;/code&gt; retrieves only the identifier from an &lt;code&gt;Order&lt;/code&gt; object. The resulting logs become much more useful:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Starting: Getting order 123
Finished: Getting order 123
Starting: Creating invoice for order 123 with a 14-day payment term
Finished: Creating invoice for order 123 with a 14-day payment term
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I chose not to append every argument automatically. A list of costs, attachments, or the entire order object could generate enormous entries and, worse, expose personal data, tokens, or passwords. The template forces a deliberate choice of the information needed to diagnose a problem—usually an order identifier, the operation name, and the new status. It is also important to remember that a typo in a template field name raises a &lt;code&gt;KeyError&lt;/code&gt;, so decorators like this should be covered by tests.&lt;/p&gt;
&lt;p&gt;Ultimately, a small abstraction allowed me to retain detailed, consistent logs without letting them obscure the program's actual logic. It does not solve every logging problem, but it has worked very well in code that integrates numerous external services.&lt;/p&gt;
&lt;p&gt;What does logging look like in your projects? Do you prefer explicit logger calls, decorators, or another solution? Where do you draw the line between useful context and too much information in your logs?&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25521/how-to-add-logging-without-cluttering-your-code</guid>
<pubDate>Thu, 27 Aug 2026 06:55:42 +0000</pubDate>
</item>
<item>
<title>Effective Logging in Go: Best Practices and Implementation Guide</title>
<link>https://coderlegion.com/22122/effective-logging-in-go-best-practices-and-implementation-guide</link>
<description>&lt;h3 id=&quot;introduction&quot;&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Logging is an essential part of software development, allowing developers to debug applications, monitor performance, and trace issues efficiently. In Go, logging plays a crucial role in building scalable and maintainable applications.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In this blog, you’ll learn about:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The importance of logging in Go&lt;/li&gt;
&lt;li&gt;How developers use it for debugging, monitoring, and auditing&lt;/li&gt;
&lt;li&gt;Different ways to implement logging in Go&lt;/li&gt;
&lt;li&gt;Performance considerations and best practices&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By the end, you'll have a clear understanding of how to set up an efficient logging system in your Go applications.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-logging-matters-in-go-&quot;&gt;&lt;strong&gt;Why Logging Matters in Go?&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Logging is more than just printing messages to the console. It serves multiple purposes:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Debugging &amp;amp; Troubleshooting&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Logs help identify bugs, errors, and unexpected behavior in an application.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Example:&lt;/strong&gt; If an API request fails, logs can show request parameters, error codes, and stack traces for debugging.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Application Monitoring &amp;amp; Performance Tracking&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Logs provide insights into execution time, memory usage, and request handling.&lt;/li&gt;
&lt;li&gt;Helps in profiling slow functions and optimizing performance.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. Security &amp;amp; Audit Logs&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Tracks user actions (e.g., login attempts, database changes).&lt;/li&gt;
&lt;li&gt;Helps detect unauthorized access or suspicious activities.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4. Distributed Systems &amp;amp; Microservices Logging&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In microservices, logs are crucial for tracking requests across services.&lt;/li&gt;
&lt;li&gt;Helps in tracing failures in distributed systems.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;5. Compliance &amp;amp; Legal Requirements&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Many industries require logging for compliance with GDPR, HIPAA, or ISO standards.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;logging-in-go-different-approaches&quot;&gt;&lt;strong&gt;Logging in Go: Different Approaches&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Go provides multiple ways to implement logging, ranging from built-in packages to third-party logging libraries.&lt;/p&gt;
&lt;h4 id=&quot;1-using-the-standard-log-package&quot;&gt;1. Using the Standard log Package&lt;/h4&gt;
&lt;p&gt;Go’s built-in log package is simple but lacks advanced features like structured logging and log levels.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Basic Example:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package main

import (
	&quot;log&quot;
)

func main() {
	log.Println(&quot;This is a simple log message&quot;)
	log.Fatal(&quot;This is a fatal error&quot;) // Terminates the program
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;log.Println() → Prints a log message&lt;/li&gt;
&lt;li&gt;log.Fatal() → Logs message and exits the program&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;limitations-&quot;&gt;Limitations:&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;No log levels (e.g., DEBUG, INFO, WARN, ERROR)&lt;/li&gt;
&lt;li&gt;No structured logging&lt;/li&gt;
&lt;li&gt;Not ideal for large applications&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;2-using-log-logger-for-more-control&quot;&gt;2. Using log.Logger for More Control&lt;/h4&gt;
&lt;p&gt;The log.Logger type allows custom configurations like output destinations.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example: Custom Logger with File Output&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package main

import (
	&quot;log&quot;
	&quot;os&quot;
)

func main() {
	file, err := os.OpenFile(&quot;app.log&quot;, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
	if err != nil {
		log.Fatal(err)
	}
	logger := log.New(file, &quot;INFO: &quot;, log.Ldate|log.Ltime|log.Lshortfile)
	logger.Println(&quot;Application started successfully&quot;)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;advantages-&quot;&gt;Advantages:&lt;/h5&gt;
&lt;p&gt;✅ Logs to a file instead of the console&lt;br&gt;
✅ Customizable log prefix and timestamp&lt;/p&gt;
&lt;h4 id=&quot;3-using-log-slog-for-structured-logging-recommended-for-modern-go-apps-&quot;&gt;3.Using log/slog for Structured Logging (Recommended for Modern Go Apps)&lt;/h4&gt;
&lt;p&gt;Go 1.21 introduced slog, a structured logging package that enhances readability and performance.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example: Using slog for JSON Logs&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package main

import (
	&quot;log/slog&quot;
	&quot;os&quot;
)

func main() {
	logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
	logger.Info(&quot;Application started&quot;, slog.String(&quot;env&quot;, &quot;production&quot;), slog.Int(&quot;port&quot;, 8080))
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;why-slog-&quot;&gt;Why slog?&lt;/h5&gt;
&lt;p&gt;✅ Structured JSON logs for better processing&lt;br&gt;
✅ Log levels (Info, Warn, Error, Debug)&lt;br&gt;
✅ Custom attributes for better context&lt;/p&gt;
&lt;h4 id=&quot;4-using-third-party-logging-libraries-logrus-zap-zerolog-&quot;&gt;4. Using Third-Party Logging Libraries (logrus, zap, zerolog)&lt;/h4&gt;
&lt;p&gt;For production-grade applications, third-party libraries offer performance optimizations and better flexibility.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example: Logging with logrus&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package main

import (
	&quot;github.com/sirupsen/logrus&quot;
)

func main() {
	log := logrus.New()
	log.SetFormatter(&amp;amp;logrus.JSONFormatter{})
	log.WithFields(logrus.Fields{
		&quot;module&quot;: &quot;main&quot;,
		&quot;status&quot;: &quot;running&quot;,
	}).Info(&quot;Application started&quot;)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;why-logrus-&quot;&gt;Why logrus?&lt;/h5&gt;
&lt;p&gt;✅ Formatted logs (JSON, text)&lt;br&gt;
✅ Log levels &amp;amp; hooks for integrations&lt;br&gt;
✅ Performance optimized&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example: Fast Logging with zap (High Performance)&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package main

import (
	&quot;go.uber.org/zap&quot;
)

func main() {
	logger, _ := zap.NewProduction()
	defer logger.Sync()

	logger.Info(&quot;Application started&quot;, zap.String(&quot;env&quot;, &quot;production&quot;), zap.Int(&quot;port&quot;, 8080))
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;why-zap-&quot;&gt;Why zap?&lt;/h5&gt;
&lt;p&gt;✅ Extremely fast (zero-allocation logger)&lt;br&gt;
✅ Ideal for high-performance applications&lt;/p&gt;
&lt;hr&gt;
&lt;h5 id=&quot;best-practices-for-efficient-logging-in-go&quot;&gt;Best Practices for Efficient Logging in Go&lt;/h5&gt;
&lt;p&gt;1️⃣ &lt;strong&gt;Use Log Levels:&lt;/strong&gt; Always categorize logs into DEBUG, INFO, WARN, ERROR, FATAL.&lt;br&gt;
2️⃣ &lt;strong&gt;Avoid Logging Sensitive Data:&lt;/strong&gt; Exclude passwords, tokens, and personal data.&lt;br&gt;
3️⃣ &lt;strong&gt;Use Structured Logging:&lt;/strong&gt; JSON logs are machine-readable and easy to process.&lt;br&gt;
4️⃣ &lt;strong&gt;Rotate Logs:&lt;/strong&gt; Use tools like logrotate or Lumberjack to prevent log files from growing too large.&lt;br&gt;
5️⃣ &lt;strong&gt;Use Context in Logs:&lt;/strong&gt; Add request IDs, timestamps, and service names for debugging distributed systems.&lt;br&gt;
6️⃣ &lt;strong&gt;Optimize for Performance:&lt;/strong&gt; Use asynchronous logging if high throughput is required.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;additional-tip-best-practice-for-passing-a-logger-as-a-parameter&quot;&gt;Additional Tip: Best Practice for Passing a Logger as a Parameter&lt;/h4&gt;
&lt;p&gt;When passing a logger into a function, it’s best to place it as the last parameter. This makes it optional when possible and ensures that the most critical parameters come first.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;For example, instead of this:&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;func ProcessData(logger *log.Logger, data string) { ... }&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Do this:&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;func ProcessData(data string, logger *log.Logger) { ... }&lt;/code&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;conclusion-&quot;&gt;Conclusion:&lt;/h4&gt;
&lt;p&gt;Logging is an essential part of maintaining, debugging, and scaling Go applications. Whether you use the built-in log package or a structured logging library like slog, logrus, or zap, choosing the right approach can greatly impact your application's performance and maintainability.&lt;/p&gt;
&lt;p&gt; What logging practices do you follow in your Go projects? Do you have a preferred logging library? Drop a comment below—I’d love to hear your thoughts! &lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/22122/effective-logging-in-go-best-practices-and-implementation-guide</guid>
<pubDate>Wed, 08 Jul 2026 15:58:17 +0000</pubDate>
</item>
<item>
<title>How I Design a Logging Strategy for My Application (That Actually Works in Production)</title>
<link>https://coderlegion.com/13517/how-i-design-a-logging-strategy-for-my-application-that-actually-works-in-production</link>
<description>&lt;p&gt;Let’s be honest most developers don’t think about logging until something breaks.&lt;/p&gt;
&lt;p&gt;You deploy your app, everything looks fine… until users start complaining. Suddenly, you're SSH-ing into servers, adding random &lt;code&gt;console.log()&lt;/code&gt; statements, redeploying, and hoping to catch the issue in real time.&lt;/p&gt;
&lt;p&gt;I’ve been there. It’s stressful, inefficient, and completely avoidable.&lt;/p&gt;
&lt;p&gt;Over time, I’ve learned that a solid logging strategy isn’t just a “nice to have” it’s a &lt;strong&gt;core part of system design&lt;/strong&gt;. Done right, logging becomes your eyes and ears in production.&lt;/p&gt;
&lt;p&gt;In this post, I’ll walk you through &lt;strong&gt;how I design a logging strategy for my applications&lt;/strong&gt;, whether it's a simple API or a distributed system.&lt;/p&gt;
&lt;h3 id=&quot;why-logging-matters-more-than-you-think&quot;&gt;Why Logging Matters More Than You Think&lt;/h3&gt;
&lt;p&gt;Before we dive into the “how,” let’s align on the “why.”&lt;/p&gt;
&lt;p&gt;A good logging strategy helps you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Debug issues faster&lt;/li&gt;
&lt;li&gt;Monitor system behavior in real time&lt;/li&gt;
&lt;li&gt;Detect security threats&lt;/li&gt;
&lt;li&gt;Understand user interactions&lt;/li&gt;
&lt;li&gt;Improve system performance&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Without proper logs, you're essentially flying blind in production.&lt;/p&gt;
&lt;div class=&quot;div-blue&quot;&gt;
&lt;span class=&quot;alert-header&quot;&gt;Note:&lt;/span&gt;
&lt;span class=&quot;alert-body&quot;&gt;Logs are not just for debugging they’re for observability, auditing, and decision making.&lt;/span&gt;
&lt;/div&gt;
&lt;h3 id=&quot;step-1-define-what-you-actually-need-to-log&quot;&gt;Step 1: Define What You Actually Need to Log&lt;/h3&gt;
&lt;p&gt;One of the biggest mistakes developers make is &lt;strong&gt;logging everything or nothing useful&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;I start by asking:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What events are critical to my system?&lt;/li&gt;
&lt;li&gt;What failures would I need to investigate later?&lt;/li&gt;
&lt;li&gt;What actions should be auditable?&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;core-categories-i-always-log-&quot;&gt;Core Categories I Always Log:&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;1. Application Events&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Server start/stop&lt;/li&gt;
&lt;li&gt;Background jobs&lt;/li&gt;
&lt;li&gt;Scheduled tasks&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. User Actions&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Logins/logouts&lt;/li&gt;
&lt;li&gt;Transactions&lt;/li&gt;
&lt;li&gt;Key interactions&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. Errors &amp;amp; Exceptions&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Stack traces&lt;/li&gt;
&lt;li&gt;Failed API calls&lt;/li&gt;
&lt;li&gt;Validation errors&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4. Security Events&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Failed authentication attempts&lt;/li&gt;
&lt;li&gt;Permission denials&lt;/li&gt;
&lt;li&gt;Suspicious activity&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;div-blue&quot;&gt;
&lt;span class=&quot;alert-header&quot;&gt;Note:&lt;/span&gt;
&lt;span class=&quot;alert-body&quot;&gt;If it affects **system behavior, security, or user experience**, it should probably be logged.&lt;/span&gt;
&lt;/div&gt;
&lt;h3 id=&quot;step-2-use-log-levels-properly-most-people-don-t-&quot;&gt;Step 2: Use Log Levels Properly (Most People Don’t)&lt;/h3&gt;
&lt;p&gt;Not all logs are equal. That’s where log levels come in.&lt;/p&gt;
&lt;p&gt;Here’s how I structure mine:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DEBUG&lt;/strong&gt; → Deep technical details (used in development)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;INFO&lt;/strong&gt; → Normal system operations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;WARN&lt;/strong&gt; → Something unexpected, but not breaking&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ERROR&lt;/strong&gt; → Failures that affect functionality&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FATAL&lt;/strong&gt; → Critical issues that may crash the system&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;real-world-example-&quot;&gt;Real World Example:&lt;/h4&gt;
&lt;p&gt;Instead of:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;&quot;User login failed&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;level&quot;: &quot;WARN&quot;,
  &quot;message&quot;: &quot;User login failed&quot;,
  &quot;userId&quot;: &quot;12345&quot;,
  &quot;reason&quot;: &quot;Invalid password&quot;,
  &quot;ip&quot;: &quot;192.168.1.1&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&quot;div-blue&quot;&gt;
&lt;span class=&quot;alert-header&quot;&gt;Note:&lt;/span&gt;
&lt;span class=&quot;alert-body&quot;&gt;Structured, leveled logs = faster debugging and better filtering.&lt;/span&gt;
&lt;/div&gt;
&lt;h3 id=&quot;step-3-always-use-structured-logging-not-plain-text-&quot;&gt;Step 3: Always Use Structured Logging (Not Plain Text)&lt;/h3&gt;
&lt;p&gt;If you’re still logging plain strings, you’re making life harder for yourself.&lt;/p&gt;
&lt;p&gt;I use &lt;strong&gt;structured logging (JSON format)&lt;/strong&gt; because it allows logs to be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Searchable&lt;/li&gt;
&lt;li&gt;Filterable&lt;/li&gt;
&lt;li&gt;Machine readable&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;example-&quot;&gt;Example:&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;timestamp&quot;: &quot;2026-03-26T12:00:00Z&quot;,
  &quot;level&quot;: &quot;ERROR&quot;,
  &quot;service&quot;: &quot;payment-service&quot;,
  &quot;message&quot;: &quot;Transaction failed&quot;,
  &quot;userId&quot;: &quot;abc123&quot;,
  &quot;transactionId&quot;: &quot;txn_456&quot;,
  &quot;error&quot;: &quot;Insufficient funds&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This becomes incredibly powerful when integrated with tools like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ELK Stack (Elasticsearch, Logstash, Kibana)&lt;/li&gt;
&lt;li&gt;Grafana + Loki&lt;/li&gt;
&lt;li&gt;Datadog&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;div-blue&quot;&gt;
&lt;span class=&quot;alert-header&quot;&gt;Note:&lt;/span&gt;
&lt;span class=&quot;alert-body&quot;&gt;Think of logs as data, not text.&lt;/span&gt;
&lt;/div&gt;
&lt;h3 id=&quot;step-4-add-context-logs-without-context-are-useless&quot;&gt;Step 4: Add Context Logs Without Context Are Useless&lt;/h3&gt;
&lt;p&gt;A log without context is like a stack trace without a file name.&lt;/p&gt;
&lt;p&gt;Every log I write answers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Where did this happen?&lt;/strong&gt; (service/module)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Who triggered it?&lt;/strong&gt; (user ID, session ID)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;What request caused it?&lt;/strong&gt; (request ID, endpoint)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;must-have-context-fields-&quot;&gt;Must Have Context Fields:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;requestId&lt;/code&gt; (for tracing across services)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;userId&lt;/code&gt; (if applicable)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;serviceName&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;environment&lt;/code&gt; (dev, staging, prod)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;div-blue&quot;&gt;
&lt;span class=&quot;alert-header&quot;&gt;Note:&lt;/span&gt;
&lt;span class=&quot;alert-body&quot;&gt;Context turns logs into **stories you can follow**.&lt;/span&gt;
&lt;/div&gt;
&lt;h3 id=&quot;step-5-implement-centralized-logging-early&quot;&gt;Step 5: Implement Centralized Logging Early&lt;/h3&gt;
&lt;p&gt;If your logs live only on your server, you’re already in trouble.&lt;/p&gt;
&lt;p&gt;I always centralize logs using tools like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ELK Stack&lt;/li&gt;
&lt;li&gt;Grafana Loki&lt;/li&gt;
&lt;li&gt;Cloud logging (AWS CloudWatch, GCP Logging)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;why-this-matters-&quot;&gt;Why this matters:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Logs are searchable in one place&lt;/li&gt;
&lt;li&gt;You can correlate events across services&lt;/li&gt;
&lt;li&gt;You don’t lose logs if a server crashes&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;div-blue&quot;&gt;
&lt;span class=&quot;alert-header&quot;&gt;Note:&lt;/span&gt;
&lt;span class=&quot;alert-body&quot;&gt;In distributed systems (like microservices), centralized logging is non-negotiable.&lt;/span&gt;
&lt;/div&gt;
&lt;h3 id=&quot;step-6-don-t-log-sensitive-data-seriously-&quot;&gt;Step 6: Don’t Log Sensitive Data (Seriously)&lt;/h3&gt;
&lt;p&gt;This is where many teams make dangerous mistakes.&lt;/p&gt;
&lt;p&gt;Never log:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Passwords&lt;/li&gt;
&lt;li&gt;API keys&lt;/li&gt;
&lt;li&gt;Tokens&lt;/li&gt;
&lt;li&gt;Credit card details&lt;/li&gt;
&lt;li&gt;Personally identifiable information (PII)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;instead-&quot;&gt;Instead:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Mask sensitive fields&lt;/li&gt;
&lt;li&gt;Use hashing where needed&lt;/li&gt;
&lt;li&gt;Log references, not raw data&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;div-blue&quot;&gt;
&lt;span class=&quot;alert-header&quot;&gt;Note:&lt;/span&gt;
&lt;span class=&quot;alert-body&quot;&gt;Logs should help you debug not create a security breach.&lt;/span&gt;
&lt;/div&gt;
&lt;h3 id=&quot;step-7-optimize-log-volume-avoid-noise-&quot;&gt;Step 7: Optimize Log Volume (Avoid Noise)&lt;/h3&gt;
&lt;p&gt;Too many logs can be just as bad as too few.&lt;/p&gt;
&lt;p&gt;I’ve seen systems where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Logs cost more than infrastructure&lt;/li&gt;
&lt;li&gt;Important signals get buried in noise&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;my-approach-&quot;&gt;My approach:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;DEBUG&lt;/code&gt; only in development&lt;/li&gt;
&lt;li&gt;Reduce repetitive logs&lt;/li&gt;
&lt;li&gt;Sample high frequency events&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;div-green&quot;&gt;
&lt;span class=&quot;alert-header&quot;&gt;Tip:&lt;/span&gt;
&lt;span class=&quot;alert-body&quot;&gt;Log **what matters**, not everything..&lt;/span&gt;
&lt;/div&gt;
&lt;h3 id=&quot;step-8-set-up-alerts-based-on-logs&quot;&gt;Step 8: Set Up Alerts Based on Logs&lt;/h3&gt;
&lt;p&gt;Logging without alerting is passive.&lt;/p&gt;
&lt;p&gt;I connect logs to alerts so I know when something goes wrong &lt;strong&gt;before users complain&lt;/strong&gt;.&lt;/p&gt;
&lt;h4 id=&quot;examples-&quot;&gt;Examples:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Spike in &lt;code&gt;ERROR&lt;/code&gt; logs → trigger alert&lt;/li&gt;
&lt;li&gt;Multiple failed logins → security alert&lt;/li&gt;
&lt;li&gt;Service downtime → immediate notification&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tools:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Grafana Alerts&lt;/li&gt;
&lt;li&gt;Datadog Monitors&lt;/li&gt;
&lt;li&gt;PagerDuty&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;div-green&quot;&gt;
&lt;span class=&quot;alert-header&quot;&gt;Word:&lt;/span&gt;
&lt;span class=&quot;alert-body&quot;&gt;Your logs should *talk to you* when something breaks.&lt;/span&gt;
&lt;/div&gt;
&lt;h3 id=&quot;step-9-correlate-logs-with-metrics-and-traces&quot;&gt;Step 9: Correlate Logs with Metrics and Traces&lt;/h3&gt;
&lt;p&gt;Logs alone are powerful but combined with metrics and tracing, they become unstoppable.&lt;/p&gt;
&lt;p&gt;This is called &lt;strong&gt;observability&lt;/strong&gt;.&lt;/p&gt;
&lt;h4 id=&quot;the-trio-&quot;&gt;The Trio:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Logs&lt;/strong&gt; → What happened&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Metrics&lt;/strong&gt; → How often it happened&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Traces&lt;/strong&gt; → Where it happened&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;div-green&quot;&gt;
&lt;span class=&quot;alert-header&quot;&gt;Word:&lt;/span&gt;
&lt;span class=&quot;alert-body&quot;&gt;This is how you debug complex systems in minutes instead of hours.&lt;/span&gt;
&lt;/div&gt;
&lt;h3 id=&quot;step-10-treat-logging-as-a-first-class-feature&quot;&gt;Step 10: Treat Logging as a First Class Feature&lt;/h3&gt;
&lt;p&gt;This is the mindset shift that changed everything for me.&lt;/p&gt;
&lt;p&gt;Logging is not:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An afterthought&lt;/li&gt;
&lt;li&gt;A debugging hack&lt;/li&gt;
&lt;li&gt;A “we’ll add it later” task&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It’s part of your system design.&lt;/p&gt;
&lt;p&gt;Every feature I build includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Logging points&lt;/li&gt;
&lt;li&gt;Error tracking&lt;/li&gt;
&lt;li&gt;Context propagation&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;a-simple-logging-checklist-i-use&quot;&gt;A Simple Logging Checklist I Use&lt;/h3&gt;
&lt;p&gt;Before shipping any feature, I ask:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Are critical actions logged?&lt;/li&gt;
&lt;li&gt;Are errors descriptive and structured?&lt;/li&gt;
&lt;li&gt;Is sensitive data excluded?&lt;/li&gt;
&lt;li&gt;Can I trace a request end-to-end?&lt;/li&gt;
&lt;li&gt;Are logs centralized and searchable?&lt;/li&gt;
&lt;li&gt;Do alerts exist for critical failures?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If not, it’s not ready for production.&lt;/p&gt;
&lt;h3 id=&quot;final-thoughts-logging-is-your-production-superpower&quot;&gt;Final Thoughts: Logging Is Your Production Superpower&lt;/h3&gt;
&lt;p&gt;When your system scales, bugs become harder to reproduce, and users become less forgiving.&lt;/p&gt;
&lt;p&gt;That’s when your logging strategy either:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Saves you hours…&lt;/li&gt;
&lt;li&gt;Or costs you days&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;div-green&quot;&gt;
&lt;span class=&quot;alert-header&quot;&gt;Word:&lt;/span&gt;
&lt;span class=&quot;alert-body&quot;&gt;A well designed logging system gives you **confidence, visibility, and control**.&lt;/span&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h3 id=&quot;call-to-action&quot;&gt;Call to Action&lt;/h3&gt;
&lt;p&gt;If this helped you rethink your logging strategy:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Share it with your team (especially before your next production incident )&lt;/li&gt;
&lt;li&gt;Bookmark it as your logging checklist&lt;/li&gt;
&lt;li&gt;Drop a comment: &lt;em&gt;What’s the worst debugging experience you’ve had due to poor logging?&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And if you're building serious systems, start treating logging like what it is &lt;strong&gt;a core engineering discipline, not an afterthought&lt;/strong&gt;.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/13517/how-i-design-a-logging-strategy-for-my-application-that-actually-works-in-production</guid>
<pubDate>Thu, 26 Mar 2026 15:22:50 +0000</pubDate>
</item>
<item>
<title>From println to Production Logging: Internals and Performance Across Languages and the OS</title>
<link>https://coderlegion.com/12053/from-println-to-production-logging-internals-and-performance-across-languages-and-the-os</link>
<description>&lt;h3 id=&quot;tl-dr&quot;&gt;TL;DR&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;We all assume a disabled log call costs nothing. It doesn't — the level check is cheap, but any string you constructed before passing it to the logger is already gone, whether the log fires or not.&lt;/li&gt;
&lt;li&gt;Every time you see a class name and line number in a log output, something paid for that. In Java, when caller info is enabled, it's a runtime stack walk. In C and Rust, it was resolved at compile time and costs nothing at runtime. Most engineers have never had reason to think about the difference.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;logger.info(&quot;User {}&quot;, user)&lt;/code&gt; is not just cleaner syntax. It's a different evaluation model — the string is only built if the log actually fires. &lt;code&gt;&quot;User &quot; + user&lt;/code&gt; is evaluated before the logger even sees it.&lt;/li&gt;
&lt;li&gt;Async logging feels like a free upgrade. It isn't. It changes what you can trust about your logs when something crashes — and the logs you lose are exactly the ones you needed.&lt;/li&gt;
&lt;li&gt;In Rust and C/C++, a disabled log call can be removed from the binary entirely at compile time. In Java and Python, it always exists at runtime, even if it does nothing. The language made this choice.&lt;/li&gt;
&lt;li&gt;Go and C logging stacks sit closer to the OS than JVM-based logging stacks. There are fewer layers between the log call and the syscall. That distance has a cost, and it compounds under load.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thanks to LLMs I could create this: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/rajkundalia/logger-internals-java&quot;&gt;https://github.com/rajkundalia/logger-internals-java&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;section&quot;&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5g9p9z12fs0boz92f510.png&quot; alt=&quot;Gemini-Generated&quot;&gt;&lt;/h3&gt;
&lt;h3 id=&quot;why-logging-is-not-just-printing&quot;&gt;Why Logging Is Not Just Printing&lt;/h3&gt;
&lt;p&gt;Most of us haven't considered how much happens between our code calling &lt;code&gt;logger.info(...)&lt;/code&gt; and that string reaching disk: a level check, a formatter, a handler with its own buffering strategy, a lock or queue depending on sync versus async mode, a syscall into the kernel, and sometimes a second system — syslog, journald — that takes over from there. At scale, that pipeline has real cost. String formatting allocates. Synchronous file writes add latency to every thread that logs. A slow disk creates backpressure that stalls application threads. And in a distributed system where logs are your only audit trail, how that pipeline behaves during a crash is not an edge case — it is a design constraint you either chose or inherited without knowing it. None of that is obvious from a &lt;code&gt;println&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-pipeline-what-a-logger-actually-does&quot;&gt;The Pipeline: What a Logger Actually Does&lt;/h3&gt;
&lt;p&gt;Before pulling any of this apart, it helps to see the whole shape at once:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Application
    ↓
Logger
    ↓
Level Filter
    ↓
Formatter
    ↓
Appender / Handler
    ↓
Operating System
    ↓
Disk / Stream
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The application emits a log event with a level, message, and arguments. The logger checks whether the configured threshold allows the event through. If it passes, the formatter constructs the final string — interpolating placeholders, appending timestamps, resolving caller location. The appender or handler takes that string and writes it somewhere: a file, stdout, a socket, a rolling buffer. That write becomes a system call, handing control to the OS, which manages buffering and flush behavior before data actually hits disk. Each stage has cost. Each stage is a place where things can go wrong or get optimized. The rest of this post is about what happens at each one.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;log-level-filtering-internals&quot;&gt;Log Level Filtering Internals&lt;/h3&gt;
&lt;p&gt;Here's something that seems obvious until you think about it: a DEBUG log call in a hot loop, in a production service configured at INFO, runs on every single iteration. It doesn't log anything — but it doesn't disappear either.&lt;/p&gt;
&lt;p&gt;The level check itself is cheap. Each level maps to an integer, and the check is a comparison — INFO against whatever the event's level is, early return if it doesn't pass. No formatting, no allocation, no appender invocation. In Logback, higher integers map to higher severity — TRACE is 5000, ERROR is 40000. &lt;code&gt;java.util.logging&lt;/code&gt; follows the same direction but uses a different numeric scale and different level names: FINE is 500, SEVERE is 1000. The ordering is not inverted — the scales and names just don't align. Either way, the comparison is fast.&lt;/p&gt;
&lt;p&gt;What I found more interesting is where in the pipeline the check actually happens. I assumed there was one gate. There are often several. In Java's SLF4J backed by Logback, the logger checks first — that's the fast path. But appenders can have their own filter chains, meaning an event can clear the logger-level check and still be dropped downstream. This is deliberate and useful: you can send WARN and above to a file, ERROR and above to an alert sink, and everything to stdout, all from the same pipeline. But it means filtering is not a single decision — it's a sequence of decisions, each adding a small amount of overhead to events that reach it.&lt;/p&gt;
&lt;p&gt;The real cost isn't the check. It's everything you did before the call site. If you constructed a string before passing it to the logger, that work happened regardless of whether the log fires. Which is exactly why placeholder syntax exists, and why it's not just a style preference.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;how-a-logger-knows-where-it-came-from&quot;&gt;How a Logger Knows Where It Came From&lt;/h3&gt;
&lt;p&gt;You've probably never thought about how a log line knows it came from &lt;code&gt;UserService.java:142&lt;/code&gt;. It just appears. What's actually happening underneath varies so much across languages that it's worth making explicit — because the cost difference is not small.&lt;/p&gt;
&lt;p&gt;In Java, two approaches exist. The older one constructs a &lt;code&gt;Throwable&lt;/code&gt; and extracts the stack trace — the JVM walks the call stack and allocates an array of frame objects. The newer approach, &lt;code&gt;StackWalker&lt;/code&gt; introduced in Java 9, is lazy and stream-based: you only materialize the frames you actually need. Both are runtime operations with real cost, which is why caller location logging is configurable in most Java frameworks and off by default in many Logback configurations. You can see how this plays out in the reference implementation at &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/rajkundalia/logger-internals-java&quot;&gt;https://github.com/rajkundalia/logger-internals-java&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Python captures caller information as part of &lt;code&gt;LogRecord&lt;/code&gt; creation, inside &lt;code&gt;_log()&lt;/code&gt;, which is only reached after the level check passes. The depth of that inspection — whether stack info is captured, whether additional frame walking occurs — depends on configuration and what the formatter requests. The cost is not paid on every call, but it is paid at record creation time, not at formatting time.&lt;/p&gt;
&lt;p&gt;Go makes this explicit. &lt;code&gt;runtime.Caller(skip int)&lt;/code&gt; returns the file, line, and function name when you ask for it. It's a runtime operation, but controlled — you call it when you need it, rather than it being woven into every log record automatically.&lt;/p&gt;
&lt;p&gt;C and C++ sidestep runtime cost entirely. &lt;code&gt;__FILE__&lt;/code&gt; and &lt;code&gt;__LINE__&lt;/code&gt; are preprocessor macros, expanded at compile time. By the time the binary runs, those values are string literals and integers baked into the executable. No stack walking, no frame introspection, nothing.&lt;/p&gt;
&lt;p&gt;Rust takes the same approach through the log crate's macro system. &lt;code&gt;log::info!(&quot;...&quot;)&lt;/code&gt; expands at compile time to include the module path and line number as constants. The binary contains no machinery for discovering caller location — it was resolved before the program ran.&lt;/p&gt;
&lt;p&gt;The gap between compile-time resolution and runtime stack walking is the kind of thing that's invisible until you're logging at high volume. C/C++ and Rust pay nothing. Java pays on every logged event where caller info is enabled. Go pays when you ask. Most engineers pick a logging framework without knowing which of these models they've signed up for.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;placeholders-vs-string-concatenation&quot;&gt;Placeholders vs String Concatenation&lt;/h3&gt;
&lt;p&gt;These two lines look similar. They are not:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// Eager: string is built before the logger is invoked
logger.info(&quot;Connected user: &quot; + user.toString());

// Lazy: string is only built if the level check passes
logger.info(&quot;Connected user: {}&quot;, user);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the first version, the JVM evaluates &lt;code&gt;user.toString()&lt;/code&gt; and concatenates the string before the logger receives anything. If the level check drops the event — which it will, for any DEBUG or TRACE call in a production service configured at INFO — that allocation and work was wasted. At low log volumes this is invisible. Scattered through hot paths at high throughput, it accumulates.&lt;/p&gt;
&lt;p&gt;In the second version, &lt;code&gt;user&lt;/code&gt; is passed as an object reference. The logger receives the raw argument. Only if the event clears the level filter does the formatter resolve the placeholder and build the final string. &lt;code&gt;toString()&lt;/code&gt; is never called otherwise, and no intermediate string is allocated.&lt;/p&gt;
&lt;p&gt;This only matters because of how filtering works — specifically the early return discussed in the filtering section. The two design choices reinforce each other: a cheap level check creates the condition under which deferred string construction delivers its benefit. If logging were unconditional, the distinction wouldn't save anything.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;os-interaction-where-language-logging-ends-and-the-os-begins&quot;&gt;OS Interaction: Where Language Logging Ends and the OS Begins&lt;/h3&gt;
&lt;p&gt;There's a boundary in every logging pipeline that most application engineers have never had reason to think about: the point where your code hands a string to the OS and stops being in control of what happens next.&lt;/p&gt;
&lt;p&gt;When an appender writes to a file, it eventually calls &lt;code&gt;write()&lt;/code&gt; — a system call. Everything above that boundary is the language runtime: string formatting, in-memory buffering, lock acquisition. Everything below it is the kernel: its own buffers, filesystem cache, eventual persistence to disk. Crossing that boundary involves a context switch from user space to kernel space. It's not free, and it happens on every unbuffered write.&lt;/p&gt;
&lt;p&gt;This is why buffered I/O matters. Rather than one &lt;code&gt;write()&lt;/code&gt; per log line, most production logging configurations accumulate output in memory and flush periodically or when the buffer is full. Fewer syscalls, higher throughput. The trade-off: a crash can lose whatever is buffered and not yet flushed. You are always choosing between durability and throughput at that boundary, whether you know it or not.&lt;/p&gt;
&lt;p&gt;The OS also offers its own logging infrastructure — syslog on POSIX systems, journald on Linux. These are daemons that accept log messages via a socket and handle buffering, rotation, and persistence outside your application entirely. The boundary shifts: your application writes to a socket, and the daemon takes responsibility for the rest. Structured fields are first-class in journald. Log rotation is not your problem. The cost is IPC (Inter-process communication) overhead — a socket write instead of a local file write.&lt;/p&gt;
&lt;p&gt;Go and C-adjacent logging stacks sit naturally close to this boundary. Go's &lt;code&gt;os.File.Write&lt;/code&gt; is a thin wrapper over &lt;code&gt;write()&lt;/code&gt; with minimal overhead between your code and the syscall. JVM logging absolutely works at scale — but it involves more layers: GC-managed heap allocations, object creation for log events, the JVM's own I/O abstraction. Those layers add up under load.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;synchronous-vs-asynchronous-logging&quot;&gt;Synchronous vs Asynchronous Logging&lt;/h3&gt;
&lt;p&gt;At some point, most engineers configure async logging and move on. Throughput goes up, latency on application threads drops, and nothing seems worse. It feels like a free upgrade.&lt;/p&gt;
&lt;p&gt;Here's what actually changed: you no longer have a guarantee that a log line you wrote ever reached disk.&lt;/p&gt;
&lt;p&gt;Synchronous logging blocks the calling thread until the write completes. The appender acquires a lock, formats the string, calls &lt;code&gt;write()&lt;/code&gt;, releases the lock. Every log call has latency. Under high write volume to a slow disk, this becomes a bottleneck that shows up on every application thread that logs.&lt;/p&gt;
&lt;p&gt;Async logging breaks this coupling. Your thread drops an event into a queue and returns immediately. A dedicated logging thread drains the queue, formats events, and writes to the appender. Throughput increases because writes get batched. Thread latency drops to the cost of a queue insertion. This sounds like a strict improvement. It is not.&lt;/p&gt;
&lt;p&gt;The queue is bounded. Under sustained high load it fills up. At that point the framework has a decision to make: block the calling thread, drop the event, or expand the queue. Many async logging implementations are configured to drop lower-severity events under pressure unless explicitly set to block — Logback's &lt;code&gt;AsyncAppender&lt;/code&gt;, for instance, starts discarding TRACE, DEBUG, and INFO events when the queue reaches 80% capacity by default, while WARN and ERROR are retained. Which means under the conditions where your system is most stressed, in the moments just before something breaks, you may be losing the exact log lines that would have told you why.&lt;/p&gt;
&lt;p&gt;The crash case is worse. Events sitting in the queue when the application crashes never reach the appender. Your crash logs — the ones you needed most — may not exist.&lt;/p&gt;
&lt;p&gt;Async logging is worth using. It is the right choice in many high-throughput systems. But it is an architectural decision about what you are willing to lose and when. Using it without understanding the failure contract means you have made that trade without knowing it.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;compile-time-vs-runtime-filtering&quot;&gt;Compile-Time vs Runtime Filtering&lt;/h3&gt;
&lt;p&gt;Something I hadn't considered when I started this: in Java, Python, and Go, a disabled log call still exists in the binary. In Java and Python this is unambiguous — the level check runs on every call. Go's compiler is more aggressive about in-lining and dead code elimination, so the picture is less clear-cut and depends on the logging library and how it's implemented. But in none of these languages can the call be eliminated entirely at compile time the way it can in Rust or C/C++.&lt;/p&gt;
&lt;p&gt;Take a TRACE call inside a hot loop in a Java service configured at INFO. On every iteration, the JVM executes an integer comparison and branches. The call is suppressed, but it was visited. At high enough frequency, that cost appears.&lt;/p&gt;
&lt;p&gt;In Rust and C/C++, this can be eliminated entirely. A &lt;code&gt;trace!()&lt;/code&gt; macro in Rust, conditioned on a compile-time feature flag, is removed by the compiler if tracing is disabled at build time. The instruction does not exist in the binary. There is no branch, no comparison, no overhead of any kind. The code was removed before the program ran.&lt;/p&gt;
&lt;p&gt;The trade-off is operational flexibility. A Java application can change its log level at runtime — attach to a running JVM, set the Logback threshold to TRACE, watch debug output appear without a restart. A C binary compiled with TRACE disabled cannot do this. The capability is gone. You traded dynamic observability for zero runtime cost.&lt;/p&gt;
&lt;p&gt;Which is right depends on context. A long-running service that needs live level adjustment values the runtime flexibility. A systems program where every cycle matters may prefer compile-time elimination. Most languages make this choice implicitly, as part of how their logging ecosystem is designed. It is worth knowing which choice your language made for you.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;cross-language-comparison-table&quot;&gt;Cross-Language Comparison Table&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Language &lt;/th&gt;&lt;th&gt;Caller Detection &lt;/th&gt;&lt;th&gt;Filter Type &lt;/th&gt;&lt;th&gt;Async Ecosystem &lt;/th&gt;&lt;th&gt;Compile-time Elimination&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Java &lt;/td&gt;&lt;td&gt;StackWalker / Throwable &lt;/td&gt;&lt;td&gt;Runtime &lt;/td&gt;&lt;td&gt;Logback AsyncAppender &lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Go &lt;/td&gt;&lt;td&gt;runtime.Caller &lt;/td&gt;&lt;td&gt;Runtime &lt;/td&gt;&lt;td&gt;zap, zerolog (non-block) &lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Python &lt;/td&gt;&lt;td&gt;currentframe / LogRecord &lt;/td&gt;&lt;td&gt;Runtime &lt;/td&gt;&lt;td&gt;QueueHandler &lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;C/C++ &lt;/td&gt;&lt;td&gt;&lt;strong&gt;FILE&lt;/strong&gt;, &lt;strong&gt;LINE&lt;/strong&gt; macros &lt;/td&gt;&lt;td&gt;Runtime / Compile &lt;/td&gt;&lt;td&gt;spdlog async mode &lt;/td&gt;&lt;td&gt;Yes (preprocessor)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Rust &lt;/td&gt;&lt;td&gt;Compile-time macro expansion &lt;/td&gt;&lt;td&gt;Runtime / Compile &lt;/td&gt;&lt;td&gt;tracing crate &lt;/td&gt;&lt;td&gt;Yes (feature flags)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;markers-in-java-slf4j-a-brief-callout&quot;&gt;Markers in Java/SLF4J — A Brief Callout&lt;/h3&gt;
&lt;p&gt;Log levels give you one axis for filtering: severity. But severity alone can't answer a question like &quot;show me all security-related events, regardless of level.&quot; That's what Markers solve. In SLF4J, a Marker is a named tag attached to a log event — SECURITY, AUDIT, BILLING — that appenders can filter on independently of level. You can route all AUDIT-marked events to a dedicated file while dropping untagged DEBUG events entirely. It's multi-dimensional filtering: level is one axis, marker is another. Other ecosystems approximate this — Go's zap uses structured fields, Python's logging has Filter objects that can inspect arbitrary LogRecord attributes — but SLF4J Markers are one of the cleaner formulations of the idea, and they're underused in codebases that reach for custom log levels when what they actually need is a second axis.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-surprised-me&quot;&gt;What Surprised Me&lt;/h3&gt;
&lt;p&gt;We all assume async logging was a performance upgrade with no real downside. It's a trade — lower latency on application threads in exchange for weaker guarantees about what survives a crash. That trade is often worth making. It's not invisible.&lt;/p&gt;
&lt;p&gt;I didn't expect caller detection to have such variance across languages. The gap between &lt;code&gt;__FILE__&lt;/code&gt; resolved at compile time and &lt;code&gt;StackWalker&lt;/code&gt; walking the call stack at runtime is not a footnote — it's an architectural difference that shows up under load, and most engineers pick a logging framework without knowing which model they've chosen.&lt;/p&gt;
&lt;p&gt;Filtering being a pipeline of gates, not a single check, was more nuanced than I expected. I assumed one threshold, one decision. In practice, logger-level filters and appender-level filters can conflict, and events can be dropped at multiple points for different reasons.&lt;/p&gt;
&lt;p&gt;The syscall boundary reframed how I think about logging performance. Everything above it is yours — allocations, formatting, buffering. Everything below it is the kernel's. Understanding where that boundary sits, and how often you cross it, makes the buffering trade-offs obvious in a way they weren't before.&lt;/p&gt;
&lt;p&gt;Compile-time log elimination felt genuinely strange when I first understood it. The log crate in Rust doesn't just suppress a call when a level is disabled — the code is removed entirely from the binary by the compiler. That's a fundamentally different model from anything Java or Python offer, and it matters in contexts where it matters.&lt;/p&gt;
&lt;p&gt;Markers are really interesting. The logs that are easiest to reason about in production are the ones where someone thought carefully about how to filter them — not just what level to assign, but what category they belong to. It's a small design decision that compounds over time.&lt;/p&gt;
&lt;h3 id=&quot;section-1&quot;&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tta7cskyt86plg0xm4nf.png&quot; alt=&quot;Notebook-LM&quot;&gt;&lt;/h3&gt;
&lt;h3 id=&quot;resources&quot;&gt;Resources&lt;/h3&gt;
&lt;p&gt;These are the rabbit holes that led here.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://stackoverflow.com/questions/26949503/how-exactly-is-the-logger-a-singleton-and-how-are-different-log-files-created-i&quot;&gt;https://stackoverflow.com/questions/26949503/how-exactly-is-the-logger-a-singleton-and-how-are-different-log-files-created-i&lt;/a&gt; — The good old StackOverFlow had a question regarding this.&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.oracle.com/javase/6/docs/technotes/guides/logging/overview.html&quot;&gt;https://docs.oracle.com/javase/6/docs/technotes/guides/logging/overview.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.reddit.com/r/java/comments/rdv98z/have_you_ever_wondered_how_javas_logging/&quot;&gt;https://www.reddit.com/r/java/comments/rdv98z/have_you_ever_wondered_how_javas_logging/&lt;/a&gt; — Down the memory lane.&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.loggly.com/ultimate-guide/java-logging-basics/&quot;&gt;https://www.loggly.com/ultimate-guide/java-logging-basics/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.marcobehler.com/guides/java-logging&quot;&gt;https://www.marcobehler.com/guides/java-logging&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://signoz.io/guides/java-log/&quot;&gt;https://signoz.io/guides/java-log/&lt;/a&gt; — table for log level is very good&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/pinojs/pino&quot;&gt;https://github.com/pinojs/pino&lt;/a&gt; — JS Library for logging&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://davidagood.com/logging-in-java/&quot;&gt;https://davidagood.com/logging-in-java/&lt;/a&gt; — Java's logging is crazy&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/TheTechGranth/thegranths/tree/master/src/main/java/SystemDesign/LoggingFramework&quot;&gt;https://github.com/TheTechGranth/thegranths/tree/master/src/main/java/SystemDesign/LoggingFramework&lt;/a&gt; — a good basic logger&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.youtube.com/watch?v=hOzH7ecc8vg&amp;amp;t=2s&quot;&gt;https://www.youtube.com/watch?v=hOzH7ecc8vg&amp;amp;t=2s&lt;/a&gt; — a good explanation for LLD for logger&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.youtube.com/live/QV4O9u1N_XU?si=lO4YYFxf-jOk5tTb&quot;&gt;https://www.youtube.com/live/QV4O9u1N_XU?si=lO4YYFxf-jOk5tTb&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://algomaster.io/learn/system-design/logging&quot;&gt;https://algomaster.io/learn/system-design/logging&lt;/a&gt; — logging best practices&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/12053/from-println-to-production-logging-internals-and-performance-across-languages-and-the-os</guid>
<pubDate>Sun, 22 Feb 2026 16:05:58 +0000</pubDate>
</item>
<item>
<title>Python Debugging Techniques</title>
<link>https://coderlegion.com/257/python-debugging-techniques</link>
<description>&lt;p style=&quot;text-align: justify; margin: 1;&quot;&gt;While developing a software application in one phase, you &lt;strong&gt;need to ensure&lt;/strong&gt; that it is &lt;strong&gt;flawless and error-free&lt;/strong&gt;. Therefore, to check that the functionality works as expected, we &lt;strong&gt;debug the codebase&lt;/strong&gt;. In SDLC, debugging occurs after successful completion of the software testing phase. In addition, the errors found during the testing phase were fixed by debugging the code. Hence, &lt;strong&gt;successful debugging means the complete uncovering of errors&lt;/strong&gt; and the fixation of bugs, which makes the application robust and efficient. That is why debugging is important in SDLC.&lt;/p&gt;
&lt;h3 style=&quot;margin: 0.5;&quot;&gt;Overview of Common Types of Bugs in Python Programs&lt;/h3&gt;  
&lt;p style=&quot;text-align: justify; margin: 1;&quot;&gt;There are various types of bugs in the Python program that can be fixed by debugging the code. Common types of bugs you may encounter are syntax, runtime, logical, name, type, index, and filenotfound errors, where each error indicates a different type of problem in the code but can be &lt;strong&gt;resolved or fixed by code debugging&lt;/strong&gt;.&lt;/p&gt; 
&lt;p&gt;&lt;img src=&quot;https://logiclair.org/?qa=blob&amp;amp;qa_blobid=16746106380802658379&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify; margin: 1;&quot;&gt;Therefore, in this article, I will cover the details of Python-oriented debugging, with information on debugging tools and the process of debugging your code using different techniques and strategies. Let’s get started.&lt;/p&gt;
&lt;h2 style=&quot;margin: 0.5;&quot;&gt;Understanding Python's Debugging Tools&lt;/h2&gt;  
&lt;p style=&quot;text-align: justify; margin: 1;&quot;&gt;To debug code effectively, understanding Python debugging tools and setting up the environment is crucial. Here, I will discuss three different tools that can help you debug your Python code efficiently.&lt;/p&gt;  
&lt;h3 style=&quot;margin: 0.5;&quot;&gt;1. Introduction to Python's Built-in Debugging Tools&lt;/h3&gt;
&lt;p style=&quot;text-align: justify; margin: 1;&quot;&gt;The &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.python.org/3/library/pdb.html&quot; title=&quot;file&quot;&gt;pdb&lt;/a&gt; module is a Python built-in debugger that is part of the Python standard library. It provides a step-by-step method for debugging the code by importing the library. This is a powerful module that allows the Python developer to &lt;strong&gt;pause the execution of the code at a breakpoint&lt;/strong&gt;. Breakpoints are the setup of specific points and then evaluate the values of variables and logic of the conditions or expression. Furthermore, it has a very straightforward way of debugging, which is invoked using the command line interface. It is a perfect tool for simple tasks and for those debuggers who are comfortable working with cmd.&lt;/p&gt; 
&lt;pre&gt;&lt;code&gt;import pdb #import
def checker(a, b):
    pdb.set_trace() #debugging starts here
    if b &amp;gt; a:
        print(&quot;B is greater than A&quot;)
    elif b == a:
        print(&quot;B is equal to A&quot;)
    else:
        print(&quot;A is smaller than B&quot;)
a = 33
b = 200
result = checker(a,b)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;margin: 0.5;&quot;&gt;2. Exploring Third-Party Debugging Tools and IDE Integrations&lt;/h3&gt;
&lt;p style=&quot;text-align: justify; margin: 1;&quot;&gt;The second popular method for debugging Python code is to set up an &lt;strong&gt;integrated debugging environment (IDE)&lt;/strong&gt;. These debugging IDEs will ensure an effective way of writing, running, and debugging the code. Furthermore, these IDS provide you with booting features of breakpoints, variable inspection, execution workflow (with step-into, step-out, and step-over), watch expression, stack trace, and advanced code editing features that make debugging much easier. Several debugging IDEs are &lt;strong&gt;PyCharm, Spider, Visual Studio Code with Python extension, PyDev (plugin for Eclipse), JupyterLab, Wing IDE&lt;/strong&gt;, etc., and some third-party tools, including &lt;strong&gt;PySnooper and pdb++&lt;/strong&gt;.&lt;/p&gt;
&lt;div class=&quot;div-blue&quot;&gt;  
&lt;span class=&quot;alert-header&quot;&gt;Note:&lt;/span&gt;  
&lt;span class=&quot;alert-body&quot;&gt;Breakpoints are the markers set by a programmer to temporarily stop the execution for debugging purposes.&lt;br&gt;Variable inspection is checking the current value of a variable while a program is paused.&lt;br&gt;A stack trace is a report of the active stack frames at a certain point in time during program execution, typically captured when an exception occurs.
&lt;/span&gt; 
&lt;/div&gt;
&lt;h3 style=&quot;margin: 0.5;&quot;&gt;3. Configuring Debugging Environments for Efficient Debugging Workflows &lt;/h3&gt;
&lt;ol&gt;
&lt;li style=&quot;text-align: justify; margin: 1;&quot;&gt;&lt;strong&gt;Use Integrated Development Environments (IDEs)&lt;/strong&gt;: IDEs like PyCharm, Visual Studio Code, or Jupyter Notebook offer debugging tools that can streamline the debugging process.&lt;/li&gt;
&lt;li style=&quot;text-align: justify; margin: 1;&quot;&gt;&lt;strong&gt;Set Breakpoints&lt;/strong&gt;: Place breakpoints in your code to pause execution at specific points and inspect variables, helping to identify the issues.&lt;/li&gt;
&lt;li style=&quot;text-align: justify; margin: 1;&quot;&gt;&lt;strong&gt;Step Through Code&lt;/strong&gt;: Utilize features like stepping into, over, or out of functions to understand code execution flow better.&lt;/li&gt; 
&lt;li style=&quot;text-align: justify; margin: 1;&quot;&gt;&lt;strong&gt;Inspect Variables&lt;/strong&gt;: Utilize IDE features to examine variable values in real-time, aiding in the detection of unexpected behavior.&lt;/li&gt;  
&lt;li style=&quot;text-align: justify; margin: 1;&quot;&gt;&lt;strong&gt;Use Debugging Libraries&lt;/strong&gt;: Libraries like pdb or ipdb provide additional debugging functionalities to enhance your debugging workflow.&lt;/li&gt;  
&lt;li style=&quot;text-align: justify; margin: 1;&quot;&gt;&lt;strong&gt;Logging&lt;/strong&gt;: Incorporate logging statements in your code to track program flow and variable values, providing insights into the program's behavior.&lt;/li&gt;  
&lt;li style=&quot;text-align: justify; margin: 1;&quot;&gt;&lt;strong&gt;Unit Tests&lt;/strong&gt;: Write unit tests to catch bugs early in the development phase, making debugging more efficient.&lt;/li&gt;  
&lt;/ol&gt;
&lt;h2 style=&quot;margin: 0.5;&quot;&gt;Using Print Statements for Debugging&lt;/h2&gt;  
&lt;h3 style=&quot;margin: 0.5;&quot;&gt;1. Leveraging Print Statements for Basic Debugging&lt;/h3&gt;  
&lt;p style=&quot;text-align: justify; margin: 1;&quot;&gt;Debuggers are the preferred way of debugging your programs, but if you don't have access or, in some cases, your program is of a smaller level, then leveraging the &lt;strong&gt;print() &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/182/python-functions&quot; target=&quot;_blank&quot;&gt;function&lt;/a&gt; for debugging&lt;/strong&gt; is an efficient way. You can tackle many bugs with just the print statement at the right place where you think an exception or abnormal behavior might occur. This is one of the simplest and most basic ways to debug your program before setting up the entire debugging environment on your machine. Therefore, the print() function will help you debug your code and fix bugs at an early stage, which is the priority of developers.&lt;/p&gt; 
&lt;h3 style=&quot;margin: 0.5;&quot;&gt;2. Tips for Effective Use of Print() to Trace Program Flow&lt;/h3&gt; 
&lt;p style=&quot;text-align: justify; margin: 1;&quot;&gt;Print statements are versatile tool for every programming language. It not only helps you print your code output but is also used for debugging. In Python, there are some tips for the effective use of the print statement that help you trace the program flow:&lt;/p&gt;
&lt;ol&gt;
&lt;li style=&quot;text-align: justify; margin: 1;&quot;&gt;Use the print() function to &lt;strong&gt;display the output result, message, and outcome of conditional and logical&lt;/strong&gt; statements and other logging information of code that helps you understand the program flow.&lt;/li&gt;
&lt;li style=&quot;text-align: justify; margin: 1;&quot;&gt;For effective debugging, strategically place the &lt;strong&gt;print() function at the right place&lt;/strong&gt; in your code where an unexpected behavior might occur to trace the execution of the program. Then, evaluate the output to identify where the code is breaking.&lt;/li&gt; 
&lt;li style=&quot;text-align: justify; margin: 1;&quot;&gt;Utilized print() functions in &lt;strong&gt;conditional statements to verify&lt;/strong&gt; whether the logic of your program generates correct outcomes or not.&lt;/li&gt;
&lt;/ol&gt;  
&lt;h3 style=&quot;margin: 0.5;&quot;&gt;3. Printing Variable Values and Debugging Output Formatting&lt;/h3&gt;  
&lt;p style=&quot;text-align: justify; margin: 1;&quot;&gt;Suppose we have a function that calculates the &lt;strong&gt;average of a list of integers&lt;/strong&gt;. However, the output is not what we expected, therefore we need to debug the code using the print statements. This is how we can use the print statement:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def average(nums): #function for calculating the average of a list
    sum = 0
    for list in nums:
        sum += list
    print(f&quot;Sum of List&quot;, sum) #to check that the sum is not zero 
    print(f&quot;Length of List&quot;, len(nums)) # to check that the list size is not zero
    return sum / len(nums)

number = [5,3,8,9,2,7]
#the format for print the output for effective readability
print(f&quot;The Average of {number} is:&quot;, average(number)) 
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify; margin: 1;&quot;&gt;In this example, we have a function called average() that takes the list as a parameter and then returns the average. We created a list to test the function. Next, we use the &lt;strong&gt;print statement at specific points&lt;/strong&gt;, displaying the values of the sum alongside the length of the list. This allows us to &lt;strong&gt;evaluate the output and quickly&lt;/strong&gt; verify the point that deviates from the program's normal flow. In addition, &lt;strong&gt;f-strings&lt;/strong&gt; are used in the program for effective readability and to follow the &lt;strong&gt;debugging output formatting&lt;/strong&gt;.&lt;/p&gt;
&lt;div class=&quot;div-green&quot;&gt;  
&lt;span class=&quot;alert-header&quot;&gt;Tip:&lt;/span&gt;  
&lt;span class=&quot;alert-body&quot;&gt; Always start debugging small sections of your code once at a time to isolate issues and make troubleshooting more manageable. 
&lt;/span&gt; 
&lt;/div&gt;
&lt;h2 style=&quot;margin: 0.5;&quot;&gt;Debugging with Logging&lt;/h2&gt;  
&lt;h3 style=&quot;margin: 0.5;&quot;&gt;1. Introduction to Python's Logging Module&lt;/h3&gt;  
&lt;p style=&quot;text-align: justify; margin: 1;&quot;&gt;For small programs, the print() function is completely fine but cannot be the best practice, when dealing with &lt;strong&gt;large Python programs that have complicated logic&lt;/strong&gt;. Therefore, you should have a &lt;strong&gt;persistent log that stores all the data of your program&lt;/strong&gt; such as information, warnings, exceptions, and errors. Python has introduced a built-in module for &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.python.org/3/library/logging.html&quot; title=&quot;file&quot;&gt;logging&lt;/a&gt;. You need to import the library into your program, and no further installation is required.&lt;/p&gt;  
&lt;h3 style=&quot;margin: 0.5;&quot;&gt;2. Configuring Logging Levels and Formatting for Debugging&lt;/h3&gt;  
&lt;p style=&quot;text-align: justify; margin: 1;&quot;&gt;In Python, the logging &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/217/mastering-importing-modules-in-python&quot; target=&quot;_blank&quot;&gt;module&lt;/a&gt; consists of &lt;strong&gt;5 different levels&lt;/strong&gt;. Each level defines the category of the log message based on the &lt;strong&gt;type of severity&lt;/strong&gt; you encountered during the program execution. These Five levels are as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li style=&quot;text-align: justify; margin: 1;&quot;&gt;&lt;strong&gt;Debug&lt;/strong&gt;: Used in the program development phase to diagnose code issues.&lt;/li&gt;  
&lt;li style=&quot;text-align: justify; margin: 1;&quot;&gt;&lt;strong&gt;Info&lt;/strong&gt;: Used to provide a successful program case in which there is no error in the program.&lt;/li&gt; 
&lt;li style=&quot;text-align: justify; margin: 1;&quot;&gt;&lt;strong&gt;Warning&lt;/strong&gt;: Used to indicate the warning at runtime that might occur in the future. e.g. removal of module older version.&lt;/li&gt;
&lt;li style=&quot;text-align: justify; margin: 1;&quot;&gt;&lt;strong&gt;Error&lt;/strong&gt;: Used to print all errors and bugs encountered during program execution and cause the program to deviate from its normal flow.&lt;/li&gt;  
&lt;li style=&quot;text-align: justify; margin: 1;&quot;&gt;&lt;strong&gt;Critical&lt;/strong&gt;: A situation or an error in the program that violates the requirements and crashes the application suddenly.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;text-align: justify; margin: 1;&quot;&gt;Enough for logging levels? Now, understand the debugging format when you use the logging module.   
First, we &lt;strong&gt;import the logging library&lt;/strong&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import logging #import logging library
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify; margin: 1;&quot;&gt;Then, set the logging level to debug, as we are using it for debugging purposes. Here, the setting level refers to &lt;strong&gt;the level at which we want to start logging&lt;/strong&gt;. So, write debug to log all the info and start from there. &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;logging.basicConfig(level=logging.DEBUG, format = '%(name)s - %(levelname)s - %(message)s') # create and config the logger  
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify; margin: 1;&quot;&gt;Then, you can use all the levels for printing the actual messages like this. &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;logging.debug(&quot;A Debug Logging Message&quot;)  
logging.warning(&quot;A Warning Logging Message&quot;)  
logging.error(&quot;An Error Logging Message&quot;)  
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify; margin: 1;&quot;&gt;That is how you set up the log messages at the debug level, and I hope you understand the basic formatting of debugging using logging. &lt;/p&gt;
&lt;h3 style=&quot;margin: 0.5;&quot;&gt;3. Using Logging to Trace Program Execution and Capture Errors&lt;/h3&gt;
&lt;p style=&quot;text-align: justify; margin: 1;&quot;&gt;Here, we will understand how to &lt;strong&gt;trace the program execution&lt;/strong&gt; and capture the error message using the logging module with the help of a coding example. Suppose we have a program with a division function, but encounter an error of &lt;strong&gt;zero division exception&lt;/strong&gt;. For this, we need to debug the code to understand the complete log of the program execution including the &lt;strong&gt;timestamp and the line of code&lt;/strong&gt; where the error occurs using the logging module instead of the print statement. &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import logging #import logging library
#configure logging with debug mode
logging.basicConfig(filename = 'file.log', filemode='w', level=logging.DEBUG, format = '%(name)s - %(asctime)s - %(levelname)s - %(message)s') # create and config the loger 

#function that can contain an error
def division(num1, num2):
    try:
        num3 = num1 / num2
    except ZeroDivisionError as e:
        logging.error(&quot;Error Occur %s&quot;, e)
    else:
        logging.debug(&quot;Result is %s&quot;, num3)

#program flow
logging.info(&quot;Info Message: Program Started&quot;)
division(8,2)
division(8,0) 
logging.info(&quot;Execution Ends &quot;) 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://logiclair.org/?qa=blob&amp;amp;qa_blobid=16662534672488074393&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;div class=&quot;div-blue&quot;&gt;  
&lt;span class=&quot;alert-header&quot;&gt;Note:&lt;/span&gt;  
&lt;span class=&quot;alert-body&quot;&gt;Understand the error message carefully because it will guide you to the root cause of the error occurrence.
&lt;/span&gt; 
&lt;/div&gt; 
&lt;p style=&quot;text-align: justify; margin: 1;&quot;&gt;In the output, it clearly shows that the first function call gives the correct output, but when the second time we call the same function with different values the error occurs, and all the &lt;strong&gt;log info of the program execution is stored in the file&lt;/strong&gt;. So, this is how you can trace your program execution and configure all the relevant logging information for debugging and analysis.&lt;/p&gt;
&lt;h2 style=&quot;margin: 0.5;&quot;&gt;Interactive Debugging with Debuggers&lt;/h2&gt;  
&lt;h3 style=&quot;margin: 0.5;&quot;&gt;1. Using Interactive Debuggers to Inspect Code Execution&lt;/h3&gt;
&lt;p style=&quot;text-align: justify; margin: 1;&quot;&gt;Now, let's move towards interactive debuggers, by which you can debug your code and find the &lt;strong&gt;result upon each execution&lt;/strong&gt;. Here, I use VS code with the Python extension for interactive debugging. In addition, I will discuss a step-by-step guide to debug the simple Python code for a better understanding of VS code.&lt;/p&gt; 
&lt;h3 style=&quot;margin: 0.5;&quot;&gt;1-1. Open Interactive Debugger&lt;/h3&gt; 
&lt;p style=&quot;text-align: justify; margin: 1;&quot;&gt;Begin by opening VS code on your computer, then move toward your Python code directory. After that, open your code file. The content inside my code file is:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;even_numbers = [2,4,6,8,10] #list of even number
square = (num ** 2 for num in even_numbers) #generator expression in single line
for numbers in square:
    print(numbers)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;margin: 0.5;&quot;&gt;1-2. Setting Breakpoints&lt;/h3&gt;  
&lt;p style=&quot;text-align: justify; margin: 1;&quot;&gt;In your Python code, set the &lt;strong&gt;breakpoint by clicking on the left margin&lt;/strong&gt; of the line numbers from where you want debugging to start. Here, I am setting the three breakpoints.&lt;/p&gt; 
&lt;p&gt;&lt;img src=&quot;https://logiclair.org/?qa=blob&amp;amp;qa_blobid=8750298902408318052&quot; alt=&quot;&quot;&gt; &lt;/p&gt;
&lt;div class=&quot;div-red&quot;&gt;  
&lt;span class=&quot;alert-header&quot;&gt;Caution:&lt;/span&gt;  
&lt;span class=&quot;alert-body&quot;&gt;Be cautious while using breakpoints because sometimes using too many breakpoints or placing them at the wrong location can cause the debugging process confusing.
&lt;/span&gt; 
&lt;/div&gt;
&lt;h3 style=&quot;margin: 0.5;&quot;&gt;1-3. Start Debugging Process&lt;/h3&gt;   
&lt;p style=&quot;text-align: justify; margin: 1;&quot;&gt;Then, click on the &lt;strong&gt;&quot;Run and Debug&quot;&lt;/strong&gt; icon in the sidebar, and choose the appropriate Python interpreter for debugging. This will start the debugging process and will pause the execution at every breakpoint you set.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://logiclair.org/?qa=blob&amp;amp;qa_blobid=13699968819958234509&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 style=&quot;margin: 0.5;&quot;&gt;1-4. Stepping Through Code&lt;/h3&gt; 
&lt;p style=&quot;text-align: justify; margin: 1;&quot;&gt;You have &lt;strong&gt;four different&lt;/strong&gt; debugging controls, that will appear when the debugging process starts. These are &lt;strong&gt;step-over, step-into, continue, and pause&lt;/strong&gt;. You can use any one of them, for executing the current line and shifting towards the next, you have to click on step over control. To step into control, you can move into the function you called on the current line. Whereas pause and continue are straightforward, so there is no need to explain.&lt;/p&gt; 
&lt;p&gt;&lt;img src=&quot;https://logiclair.org/?qa=blob&amp;amp;qa_blobid=2921894147560521310&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 style=&quot;margin: 0.5;&quot;&gt;2. Examining Variable Values and Stack Traces During Debugging Sessions &lt;/h3&gt;
&lt;p style=&quot;text-align: justify; margin: 1;&quot;&gt;You can also examine the variable values and stack traces in the debug sidebar while debugging your Python code. This will show you all the &lt;strong&gt;variables in the current scope with their values&lt;/strong&gt;. Whereas the stack traces will show the &lt;strong&gt;chain of the function calls&lt;/strong&gt; and lead you to the root that causes the error.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://logiclair.org/?qa=blob&amp;amp;qa_blobid=10721248725382501458&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;div class=&quot;div-faq&quot; style=&quot;margin-bottom: 5px; margin-top: 2px;&quot;&gt;
  &lt;span class=&quot;faq-header&quot; style=&quot;font-weight: bold; color: #333;&quot;&gt;FAQs&lt;/span&gt;
  &lt;div class=&quot;faq-body&quot; style=&quot;margin-top: 2px; margin-bottom: 10px;&quot;&gt;
    &lt;strong&gt;Q:&lt;/strong&gt; Why is debugging necessary?&lt;br&gt;
    &lt;strong&gt;A:&lt;/strong&gt; It helps you to find errors or bugs at an early stage and makes the &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/215/testing-in-python-writing-test-cases-with-unittest&quot; target=&quot;_blank&quot;&gt;testing&lt;/a&gt; phase easier.
  &lt;/div&gt;
  &lt;div class=&quot;faq-body&quot; style=&quot;margin-top: 2px; margin-bottom: 10px;&quot;&gt;
    &lt;strong&gt;Q:&lt;/strong&gt; How can I start debugging?&lt;br&gt;
    &lt;strong&gt;A:&lt;/strong&gt; After writing the code, you can use the print() function or logging or can set the breakpoints for interactive debugging.
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 style=&quot;margin: 0.5;&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p style=&quot;text-align: justify; margin: 1;&quot;&gt;Finally, we discussed that debugging is an important part of the software development phase. Mostly, developers who work on small-scale projects just &lt;strong&gt;ignore debugging and shift toward fixing the bugs&lt;/strong&gt; without finding which part caused the error. Therefore, adding debugging to  your programming part, and also &lt;strong&gt;exploring new debugging tools&lt;/strong&gt; can &lt;strong&gt;enhance your debugging process by effectively&lt;/strong&gt; reducing time consumption. We learned different strategies that include simple print() statements to logging modules, but you can &lt;strong&gt;incorporate a debugger&lt;/strong&gt; as it is the preferred choice of many developers and industries. I hope you like this guide. If you have any questions, then feel free to give feedback. Thank you for following this guide. Happy Coding!&lt;/p&gt;
</description>
<category>Best Practices</category>
<guid isPermaLink="true">https://coderlegion.com/257/python-debugging-techniques</guid>
<pubDate>Mon, 29 Apr 2024 21:28:41 +0000</pubDate>
</item>
</channel>
</rss>