I couldn't find a logging library that worked for my library, so I made one

2 months ago (hackers.pub)

Libraries shouldn't log. Just have your top-level abstractions extend an EventEmitter base, emit appropriate events, and let the user do the rest.

  • This is so simple and makes so much sense. I’ve seen a couple libraries that do something similar, but I feel like this is obvious and useful enough that it should just be a stock pattern, and it clearly isn’t.

  • Why shouldn’t libraries log?

    • Aside from what some other users have said, logging is fundamentally an observable side-effect of your library. It’s now a behavior that can become load-bearing — and putting it in library code forces this exposed behavior on the consumer.

      As a developer, this gets frustrating. I want to present a clean and coherent output to my callers, and poorly-authored libraries ruin that — especially if they offer no mechanism to disable it.

      It’s also just _sloppy_ in many cases. Well-designed library code often shouldn’t even need to log in the first place because it should clearly articulate each units side-effects; the composition of which should become clear to understand. Sadly, “design” has become a lost art in modern software development.

      1 reply →

    • It depends a lot on the language, but in my field libraries that have their own logging implementation and that don't provide hooks to override it cause big problems for me because I send all the logs to the same central logging client that forwards it to the same central logging server for aggregation. Your logging probably dumps it to a file, or it writes it to STDOUT, or something similar, in which case now I have to pipe all of that data in two places by doing something hacky.

      There are some language ecosystems that have good logging systems like Java which I would be totally fine with. But systems languages like C/C++ that don't have a core concept of a "log message" are a pain to deal with when the library author decides to stream some text message somewhere. Which is probably a good argument for not using those languages in some circles, but sometimes you have to use what you have.

      So it's not really a blanket "don't do it" but you should carefully consider whether there's some well-known mechanism for application authors to intake and manage your logging output, and if that doesn't exist you should provide some hooks or consider not logging at all except with some control.

    • It tends to break composability.

      The behavior of the library logging can be incompatible with the architectural requirements of the application in dimensions that are independent of the library functionality. It requires the user to understand the details of the library's logging implementation. Even if documented, design choices for the logging will automatically disqualify the library for some applications and use cases.

      Is writing the log a blocking call? Does it allocate? Synchronous or async? What is the impact on tail latencies? How does it interact with concurrency? Etc.

    • Because they have to be compatible with your logging implementation, and they were written first.

Almost every logger in java operates this way. You set your library logging to debug and the end user and configure if they want debug logs from your library or not. They can even set context variables.

  • Python too. Honestly, any mature logger should allow embedding logs in library code that can be turned on or off by the end user. This was a solved problem 20 years ago. I honestly don't see what's so novel about this today. Or is this speaking to the sorry state of software engineering that plagues the JavaScript world?

    • Rust also.

      Similar to this: OpenTelemetry has a golang library that allows you to instrument your library so that you can send traces, logs, etc. to an OTEL endpoint, but that instrumentation doesn't actually do anything unless the developer of the actual application uses a separate golang library to actually trigger, collect, and transmit those traces. Otherwise it's basically a no-op.

This feels a bit like a pub/sub pattern; I wonder what it would look like with a full pub/sub implementation.

industry-proven and mature libs like LOG4J or LOG4Net are not sufficient?

  • In the .Net space log4net is horrifically outdated and there's zero reason to use it today. Logging for modern .Net apps and libraries should be built on the Microsoft.Extensions.Logging abstractions which provide the type of features covered in TFA. They also provide a clear separation between generating log events in code and determining where & how logs are stored. For basic needs you can use simple log writers that tie in directly with MEL, or for advanced needs link MEL with Serilog so that you can use its sinks and log processing pipeline.

  • You mean this log4j [0] with major vulnerabilities the industry missed for nearly a decade?

    [0] https://en.wikipedia.org/wiki/Log4Shell

Wasn't OpenTelemetry invented for this purpose?

  • The JS impl of OpenTel is awfully glued together. Sorry for the maintainers, but its inefficient and prometheus+logging will outmatch OT any day of the week (at least for JS)

    • Which OpenTel impl isn't awfully glued together? When I looked at Rust implementation... I felt really bad. I feel like rolling my own logging is the only option.

      1 reply →

I really want something like this to be built into the language or runtime, I don't want to juggle configuration for 4 different libraries. Log4j and tracing seem to be well established without being built in, but it feels too late for js.

I'm curious if this is enough https://nodejs.org/api/diagnostics_channel.html

I don't like the js hotel libraries, their docs feel deliberately obtuse

So many knee jerk comments from people who have not read the article. The author describes how existing logging systems are geared towards applications, and sometimes libraries need a way of logging that provides more granularity and control - especially when the library spans multiple levels of abstraction.