Jamie Tanna

@www.jvt.me.web.brid.gy

Looking at how logs being the main interface for understanding how tools work can be a blessing and a curse.

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.

jvt.me

Listened to Breaking Change v49 - Saving Face Oil by Justin Searls Post details > I'm about to get on a plane and will be gone for a couple weeks, but didn't want to leave you Breaking Changeless so I did the thing where I stand up in front…

Breaking Change v49 - Saving Face Oil

I'm about to get on a plane and will be gone for a couple weeks, but didn't want to leave you Breaking Changeless so I did the thing where I stand up in front…

justin.searls.co

A known documented, but maybe lesser known, fact about GitHub Custom Properties' values and descriptions being available to anyone with `read` access.

Gotcha: GitHub Custom Properties are public

I was speaking to someone the other day - while I've been at Open Source Summit (Europe) - about the great Custom Properties that GitHub added in ~2023. However, they weren't aware that they're available to anyone with read access to the repo - which means for a public repo, that's everyone! This was something we found at Elastic while we were rolling out something that sounds pretty juicy called `security_level`. This was a mandatory field across all our repos, which was set to a reasonable default value. We ideally wanted to have our Custom Properties' descriptions include more context about what the security level fields meant, and a link to our internal docs for more in-depth information and how to change it. At Elastic we had a lot of Open Source (and public repos which are not-Open Source), which meant that anyone would be able to look up the descriptions of these fields. We can see this in action on i.e. `github/gh-stack` by using the API: [ { "property_name": "client-app", "value": "false" }, { "property_name": "CodeQL-Block", "value": "true" }, { "property_name": "dependency-review-action-enabled", "value": "true" }, { "property_name": "deployable", "value": "false" }, { "property_name": "durable-ownership-check-enabled", "value": "false" }, { "property_name": "ownership-name", "value": "@github/pull-requests" }, { "property_name": "ownership-type", "value": "Team" }, { "property_name": "repo-mirror-ci", "value": "disabled" }, { "property_name": "repo-mirror-strategy", "value": "standard" } ] You can also see this in the web UI, which can be found on each repo's homepage, following the link to "Custom Properties". Notice that in the web UI, you can see the description of the field, which isn't - as far as I can find - available through an API call if you're not part of the organisation. This does appear to be visibly documented by GitHub - but is something folks seem to not be aware of!

jvt.me

What happened in the week of 2026-09-28?

Week Notes 26#40

* A chilled journey back home, with a stop-off at Blakeney for a walk with the dogs * We bumped into Dougie on the road home which was a nice surprise! * Finally looked into a massive papercut I have with my Neovim + Vale setup, where I need to stop Vale and then clear LSP diagnostics, which Claude Sonnet 5 found as an upstream issue in Vale's LSP, which was fixed by the maintainers * Was cool to see my post about why I - still - think Renovate is the best tool for the job appear on Lobsters * This week I celebrated my blog's 10th anniversary - I could've spent a lot of more time reflecting but also maybe want a bit more time to think about what to say - maybe it'll even be a private post just for me * Started on the TfL jigsaw, which is going well * A busy week at work, announcing some big security advisories and prepping for Open Source Summit * Did a bit of life admin, like finally sorting out my phone contract, upgrading to Monzo Max and getting Discord Nitro * Had a good time at Phil Wang on Saturday - a few bits that were the same as his bit in last year's Christmas gig, but still good! * James Trickey was a good warmup * A nice time at pizza Thursday, but felt like I didn't really get a chance to talk to anyone! * A busy weekend, as on Friday evening, we noticed that one of our radiators had been leaking (enough so that we found out by water dripping through the floorboards into the hallway) - I've managed to change the lockshield valve, but there's a lot of water we're drying out up there * (Most likely) Morph brought in a very dead mouse one day 😅 Playing: * (No _Blue Prince_) * _Kingshot_ (per usual) * Looking forward to KvK! * _Apex Legends_ * The Street Fighter mode being its own queue meant _such long_ wait times that I ended up playing regular Wildcard Reading: * _The Sins of Our Fathers_ Watched: * _Neagley (Series 1)_ * _The Expanse (Season 5)_ * _Taskmaster (Series 22)_ * (No _Furiosa: A Mad Max Saga (2024)_) * _The Celebrity Traitors (Series 2)_ * _Ted Lasso (Season 1)_ * _Between Two Ferns (on YouTube)_

jvt.me