Logs as UX

A few years ago, Ryan wrote about how Bridgy, a service I use a lot, exposes log as the end user UI. This is a post that's stuck with me over the years, especially as someone whose work has generally comprised of building tools for others.

I think about this post every so often with how in Renovate, our logs are the interface that users, operators, and support teams get into understanding what's going on under the hood.

More generally, I think that focussing on your user-facing logs can be a key area to improve the experience for users of your software.

(This make sense for a subset of applications - if you're building a SAAS platform or a mobile app, you may have different avenues to expose this information)

They're there 🀷

At the laziest level, it's pretty likely that the software you're using already has logs available - often to be provided to the vendor to help support your usage.

These logs are often written to disk in plain text, which means you as a human can read them without any extra tooling, making them more accessible for debugging.

You may need extra configuration to increase the log level (see below) but it's often something within your control.

Logs as an empathy builder

As well as being the Renovate Project Lead, I am also the Community Manager for the project.

One of the aspects of being the Community Manager - as well as part of my internal role at Mend including supporting Mend's Enterprise customers - is that I work through issues our other users are seeing with their Renovate setups. As part of this, I spend a chunk of time reading through debug logs, working to understand the situation they're in, and seeing how we can unblock them (in the short term) and stop it from happening (in the medium term).

This puts me - and the other collaborators and maintainers on the project - in a particularly strong position to increase our empathy for our users. I could argue that Renovate's logs are "the great equaliser", because for me - as someone who supports Mend-hosted apps for > 1 million repositories for our customers and free users alike - I have exactly the same debug logs as a user who runs the Renovate CLI themselves. This means that if I can't work out what an issue is with the information in our logs, I can feel confident others will not either.

Not only do we spend a lot of our time debugging with those logs, but also hearing from users' questions and/or feedback, we can understand where there is a gap in understanding between us (as folks more familiar with Renovate's internals) and users who are likely not as familiar.

As an example, Renovate's JSON log objects are fairly straightforward to read, and the most minimal version of a log would be i.e.

{
  "hostname": "caerbannog",
  "level": 20,
  "logContext": "cdc129f3-d03a-4501-8168-d7d54a874601",
  "msg": "Using RE2 regex engine",
  "name": "renovate",
  "pid": 73914,
  "time": "2026-09-04T16:10:05.909Z",
  "v": 0
}

Where we have extra information that is useful to share, we'll add it into extra keys on the JSON object (like renovateUsername) or with nested JSON objects (like platformConfig):

{
  "hostname": "caerbannog",
  "level": 20,
  "logContext": "cdc129f3-d03a-4501-8168-d7d54a874601",
  "msg": "Platform config",
  "name": "renovate",
  "pid": 73914,
  "platformConfig": {
    "host": {
      "apiUrl": {
      },
      "type": "github"
    },
    "isGHApp": false,
    "userDetails": {
      "email": null,
      "id": 3315059,
      "name": "Jamie Tanna",
      "username": "jamietanna"
    },
    "userEmail": null
  },
  "renovateUsername": "jamietanna",
  "span_id": "cba6adce61f91cd4",
  "time": "2026-09-04T16:10:06.682Z",
  "trace_flags": "01",
  "trace_id": "323ee7f13437b849dd4dbd3ad00f94ae",
  "v": 0
}

Over the years we've improved - and continue to improve - what information we present in our logs, and each time we find a situation where it'd be more useful to have a debug log to investigate what's going on, we'll add it in, whether as a new log line, or as a new key in the JSON object that we already output.

We realise that there's always room to improve, and something that makes sense to us may not make as much sense to someone with a slight familiarity with Renovate - we very much welcome suggested improvements.

I'll also note that in the last year, we're made some significant investments in improvements to our OpenTelemetry traces, which provide a slightly different window into the same underlying Renovate process, which I'm currently using to investigate some key areas for improvements on how Renovate runs on large repositories.

Logs require curation

Having a system that is observable is the goal for many operators of systems. But one of the negative areas about adding any level of observability - aside from having a debugger attached to a process in production - is that it requires prior consideration. A log message, tracing span or outputted metric only exists because someone has written one in the codebase, and that's trickled through into the release version you're now running in production.

Unfortunately, this also means that when this improved logging is available in a newer version of the software, it's often not possible to apply that retrospectively to an existing run of the software, so you need to i.e. hit that error case again to understand what's going on.

Although I'm fortunate that I have a bit more weight in being able to contribute new logs to Renovate, as software engineers (and users of software) we generally don't have access to modify all our software, which means it's not always possible to contribute new logging to the tools we depend on.

I think we strike a good balance with Renovate, as we do consider what logs we have and how we want to present them to the user, and provide a bit more of a considered log than Bridgy's (intentionally) more terse log format.

--very-very-verbose

With Renovate, we try and keep our most important information in the INFO log level (and above), so operators and users can be sure that they only see the most actionable information at higher log levels.

That being said, we still recommend that self-hosted administrators use the DEBUG log level where possible, as using DEBUG log level for all Mend-hosted users, as there is a lot of useful information in there, which isn't quite worthy of being promoted up to INFO.

I was recently chatting with a Renovate user who (rightfully) described our logs as:

renovate debug logs are both verbose and illuminating

This is a fair assessment, and some of it is a byproduct of our decision to keep our most actionable information in INFO+ log lines, meaning we have a lot of information that we relegate to the DEBUG log level (or to TRACE)).

We can't predict exactly which lines a given user would want, and want to avoid making our INFO+ logs too noisy, but there's definitely a balance here, on top of the constraints we've put on ourselves, and we do periodically review what could be elevated to an INFO log line.

One thing I miss about my days in Java was that with SLF4J (and the logging adapters like Log4J) you could provide per-package logging configuration, allowing you to hone in on authentication-handling code, but ignore more in-depth debugging for filesystem operations.

If you're able to expose some more control to your users around exactly what gets logged, that can provide a lot of extra control and value, which can help reduce some of the verbosity that some users may find, as they can then turn off areas they're not interested in.

(Renovate does have the logLevelRemap config option to move logs from one level to another, which isn't quite the same)

Is it even safe to turn up the log level?

As I've talked about before, there are a lot of people logging things they really shouldn't be.

Depending on what software you're using, it may not be safe to enable DEBUG logging, because the logs may expose secrets or private information that shouldn't be seen outside of non-production environments.

Whereas tools like Renovate work very hard to sanitise secrets that hit our logs, not all tools are equal.

Back when I worked at Capital One, we couldn't turn on DEBUG logging for our identity server because the commercial-off-the-shelf solution we had for it would log full cookies, access tokens, and more - which isn't ideal when this meant someone could use that to access customer data πŸ˜… So instead, it meant we only had INFO+ logs to work with.

Log aggregators may ruin your day

One of the things I disliked greatly while working at Elastic was that all our log aggregation went through - you guessed it! - the Elastic Stack.

Now, the Elastic Stack itself is fine, but for our log aggregation, we would add additional meta information to log lines, for instance to say "this is the Kubernetes cluster, in this region, that the workload is running on."

As noted above, Renovate's JSON log objects are fairly straightforward to read, with a more complex object which looks like so:

{
  "hostname": "caerbannog",
  "level": 20,
  "logContext": "cdc129f3-d03a-4501-8168-d7d54a874601",
  "msg": "Platform config",
  "name": "renovate",
  "pid": 73914,
  "platformConfig": {
    "host": {
      "apiUrl": {
      },
      "type": "github"
    },
    "isGHApp": false,
    "userDetails": {
      "email": null,
      "id": 3315059,
      "name": "Jamie Tanna",
      "username": "jamietanna"
    },
    "userEmail": null
  },
  "renovateUsername": "jamietanna",
  "span_id": "cba6adce61f91cd4",
  "time": "2026-09-04T16:10:06.682Z",
  "trace_flags": "01",
  "trace_id": "323ee7f13437b849dd4dbd3ad00f94ae",
  "v": 0
}

The trouble is that depending on how your log ingestion is set up, it may result in large log object like:

{
  "_index": "filebeat-8.17.0",
  "_source": {
    "@timestamp": "2026-09-04T16:10:06.682Z",
    "message": "Platform config",
    "log": {
      "level": "info",
      "file": {
        "path": "/var/log/containers/renovate_caerbannog_prod_f47ac10b-58cc-4372-a567-0e02b2c3d479_renovate_9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a.log"
      },
      "offset": 46032
    },
    "renovate": {
      "name": "renovate",
      "logContext": "cdc129f3-d03a-4501-8168-d7d54a874601",
      "pid": 73914,
      "v": 0,
      "renovateUsername": "jamietanna",
      "span_id": "cba6adce61f91cd4",
      "trace_id": "323ee7f13437b849dd4dbd3ad00f94ae",
      "trace_flags": "01",
      "platformConfig": {
        "host": {
          "apiUrl": {},
          "type": "github"
        },
        "isGHApp": false,
        "userDetails": {
          "email": null,
          "id": 3315059,
          "name": "Jamie Tanna",
          "username": "jamietanna"
        },
        "userEmail": null
      }
    },
    "kubernetes": {
      "pod": {
        "name": "caerbannog",
        "uid": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
      },
      "namespace": {
        "name": "prod"
      },
      "container": {
        "name": "renovate"
      },
      "node": {
        "name": "minikube"
      },
      "labels": {
        "app": "renovate",
        "app.kubernetes.io/managed-by": "helm"
      }
    },
    "container": {
      "id": "9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a"
    },
    "host": {
      "name": "minikube"
    },
    "agent": {
      "type": "filebeat",
      "version": "8.17.0"
    },
    "input": {
      "type": "log"
    },
    "ecs": {
      "version": "8.17.0"
    },
    "tags": [
      "forwarded"
    ]
  }
}

This extra bloat is useful to some folks, but it hides a lot of the detail of what Renovate is telling the user.

Another common issue I had at Elastic was that the shared logging index that we used for all workloads would inevitably have some disagreements of what a log field's shape should be.

A common complaint with Renovate's logs - that I share - is that the level is a numeric value, instead of a string like WARN or DEBUG.

Depending on what control you have in your organisation, you may then end up in a position where if a previous workload has defined what i.e. the level field should look like, you may not be able to write complex queries on your own logs if they disagree in format.

Another issue I've seen with log aggregators is that they may ingest logs nicely, but put them into a string envelope, which results in this horrible nested JSON string that's mildly annoying to decode:

{
  // ...
  "message": "{\"name\": \"renovate\", ...}"
}

Not all logs are rendered equally

When you do have the logs, and they've not been mangled by a nasty log aggregator, how do you then view them?

Do you read the raw newline-delimited JSON logs in your editor? Do you pretty print them as formatted and syntax-highlighted JSON objects? Do you convert them to another format like Canonical Log Lines? Do you use a generic log-viewing tool like lnav?

As I've written about before, when reading Renovate's log lines, I want something that understands the domain-specific terminology and logging conventions that Renovate uses, rather than using something more generic.

This meaningfully improves my time reading logs - of which, I spend a lot of my week doing so! - and I recommend considering what other common log files you interact with that you may find useful to have some more targeted tooling for.

I'd recommend you consider building your own tools to provide an even more enjoyable experience for your tools' logs - which, if you have spare cash for the AI overlords, could be quicker than you think.

Logs as input tokens

A few years ago, if someone was in the middle of debugging and wanted to understand i.e. Renovate worked, they had a few options:

  • read the documentation (and hope it covered what they were looking for)
  • see if they could piece together any log lines from the codebase, and understand some of the flow
  • make their best guess at what's going on

In the world of AI, it's now a little more straightforward if you want to put an agent on the job - you can take the debug logs and point an LLM at them, as well as a copy of the codebase, and it'll be able to get some of the way quicker than you may have.

Now, it won't necessarily be correct, nor super efficient, but it's perhaps a little more likely to be correct than someone unfamiliar with the codebase guessing, and one option available to folks debugging.

It's an interesting development that in the last couple of years, the industry has finally been improving documentation for new contributors, but it's being driven by AI agents rather than humans who've needed this documentation for years.

Closing thoughts

I find logs to be a really valuable piece of observability into how a piece of software works, and recommend you invest in your own logs as a way to provide better user experience.

They're usually the key interface users and operators alike get into understanding how your software works, so you should "eat what you cook" and use the logs as your own primary tool for own debugging, to provide you greater empathy for your users, and understand where the gaps in observability can be improved.

Although a log aggregator may mangle the logs as they exist, I recommend explicitly designing how you want your logs to look and work. It may sound weird to think of "designing" your logs, given they're plaintext or JSON, so how much you can you really "design"? But thinking about whether you're going to use INFO level heavily or move more information into DEBUG, or how you want to design the JSON layout for your structure logging, as well as considering how you safely redact PII or secrets.

Ryan's post about how Bridgy has designed their logs to be terse and minimal, yet valuable, provides a different option to what I strive for - but both are reasonable options, as long as you're intentional with the design.

Build a more curated set of logs and your users, operators, and future-you will be appreciative. And if you decide you don't want to invest in them, or you're happy them being more terse, be intentional about that choice.

Written by Jamie Tanna's profile image Jamie Tanna on , and last updated on .

Content for this article is shared under the terms of the Creative Commons Attribution Non Commercial Share Alike 4.0 International, and code is shared under the Apache License 2.0.

#logs #observability #renovate.

πŸ€– Content in this blog post (prose or code snippets) includes code derived from the following LLMs:

  • qwen3.8:27b
  • claude:sonnet-5

This post was filed under articles.

Interactions with this post

Interactions with this post

Below you can find the interactions that this page has had using WebMention.

Have you written a response to this post? Let me know the URL:

Do you not have a website set up with WebMention capabilities? You can use Comment Parade.