{"id":23771632,"url":"https://github.com/michurin/human-readable-json-logging","last_synced_at":"2025-09-05T15:32:40.459Z","repository":{"id":212764231,"uuid":"732258803","full_name":"michurin/human-readable-json-logging","owner":"michurin","description":"The tool to pretty print JSON log stream right from running process in human readable format","archived":false,"fork":false,"pushed_at":"2025-07-12T04:57:55.000Z","size":91,"stargazers_count":25,"open_issues_count":1,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-07-12T06:25:06.063Z","etag":null,"topics":["elk","go","golang","human-readable","logging","slog","structured-logging","tooling"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/michurin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-12-16T04:37:18.000Z","updated_at":"2025-07-12T04:57:58.000Z","dependencies_parsed_at":"2023-12-17T07:19:47.774Z","dependency_job_id":"cf1a9f3b-f271-4ac4-9c38-97cf91016c0d","html_url":"https://github.com/michurin/human-readable-json-logging","commit_stats":null,"previous_names":["michurin/human-readable-json-logging"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/michurin/human-readable-json-logging","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michurin%2Fhuman-readable-json-logging","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michurin%2Fhuman-readable-json-logging/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michurin%2Fhuman-readable-json-logging/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michurin%2Fhuman-readable-json-logging/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/michurin","download_url":"https://codeload.github.com/michurin/human-readable-json-logging/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michurin%2Fhuman-readable-json-logging/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273777611,"owners_count":25166375,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-05T02:00:09.113Z","response_time":402,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["elk","go","golang","human-readable","logging","slog","structured-logging","tooling"],"created_at":"2025-01-01T04:20:31.613Z","updated_at":"2025-09-05T15:32:40.448Z","avatar_url":"https://github.com/michurin.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# human-readable-json-logging (pplog)\n\n[![build](https://github.com/michurin/human-readable-json-logging/actions/workflows/ci.yaml/badge.svg)](https://github.com/michurin/human-readable-json-logging/actions/workflows/ci.yaml)\n[![codecov](https://codecov.io/gh/michurin/human-readable-json-logging/graph/badge.svg?token=LDYMK3ZM06)](https://codecov.io/gh/michurin/human-readable-json-logging)\n[![Go Report Card](https://goreportcard.com/badge/github.com/michurin/human-readable-json-logging)](https://goreportcard.com/report/github.com/michurin/human-readable-json-logging)\n[![Go Reference](https://pkg.go.dev/badge/github.com/michurin/human-readable-json-logging.svg)](https://pkg.go.dev/github.com/michurin/human-readable-json-logging/slogtotext)\n\nIt is library and binary CLI tool to make JSON logs readable including colorizing.\nThe formatting is based on standard goland templating engine [text/template](https://pkg.go.dev/text/template).\n\nThe CLI tool obtains templates from environment or configuration file. See examples below.\n\nYou can find examples of using the library in [documentation](https://pkg.go.dev/github.com/michurin/human-readable-json-logging/slogtotext).\nLong story short, you are to direct output of your JSON logger (including modern [slog](https://pkg.go.dev/log/slog)) to magic reader and\nreadable loglines shows up.\n\n## Install and use\n\n```sh\ngo install -v github.com/michurin/human-readable-json-logging/cmd/...@latest\n```\n\nRunning in subprocess mode:\n\n```sh\npplog ./service\n# or even\npplog go run ./cmd/service/...\n```\n\nRunning in pipe mode:\n\n```sh\n./service | pplog\n# or with redirections if you need to take both stderr and stdout\n./service 2\u003e\u00261 | pplog\n# or the same redirections in modern shells\n./service |\u0026 pplog\n```\n\n## Real life example\n\nOne of my configuration file:\n\n```sh\n# File pplog.env. The syntax of file is right the same as systemd env-files.\n# You can put it into your working dirrectory or any parrent.\n# You are free to set this variables in your .bashrc as well.\n\nPPLOG_LOGLINE='\n{{- if .type     }}{{ if eq .type \"I\" }}\\e[92m{{ end }}{{if eq .type \"E\" }}\\e[1;33;41m{{ end }}{{.type}}\\e[0m {{ end }}\n{{- if .time     }}{{ if eq .type \"E\" }}\\e[93;41m{{ else }}\\e[33m{{ end }}{{.time | tmf \"2006-01-02T15:04:05Z07:00\" \"15:04:05\"}}\\e[0m {{ end }}\n{{- if .run      }}\\e[93m{{ .run | printf \"%4.4s\"}}\\e[0m {{ end }}\n{{- if .comp     }}\\e[92m{{ .comp     }}\\e[0m {{ end }}\n{{- if .scope    }}\\e[32m{{ .scope    }}\\e[0m {{ end }}\n{{- if .ci_test_name }}\\e[35;44;1m{{ .ci_test_name}}\\e[0m {{ end }}\n{{- if .function }}\\e[94m{{ .function }} \\e[95m{{.lineno}}\\e[0m {{ end }}\n{{- if .message  }}\\e[97m{{ .message  }}\\e[0m {{ end }}\n{{- if .error    }}\\e[91m{{ .error    }}\\e[0m {{ end }}\n{{- if .error_trace }}\\e[93m{{ .error_trace }}\\e[0m {{ end }}\n{{- range .ALL | rmByPfx\n    \"_tracing\"\n    \"ci_test_name\"\n    \"cluster_name\"\n    \"comp\"\n    \"env\"\n    \"error\"\n    \"error_trace\"\n    \"function\"\n    \"k8s_\"\n    \"lineno\"\n    \"message\"\n    \"run\"\n    \"scope\"\n    \"tag\"\n    \"time\"\n    \"type\"\n    \"xsource\"\n}}\\e[33m{{.K}}\\e[0m={{.V}} {{ end }}\n'\n\nPPLOG_ERRLINE='{{ if .BINARY }}{{ .TEXT }}{{ else }}\\e[97m{{ .TEXT }}\\e[0m{{ end }}'\n```\n\nMy original logs look like this:\n\n```json\n{\"type\":\"I\",\"time\":\"2024-01-01T07:33:44Z\",\"message\":\"RPC call\",\"k8s_node\":\"ix-x-kub114\",\"k8s_pod\":\"booking-v64-64cf64db6d-gm9pc\",\"cluster_name\":\"zeta\",\"env\":\"prod\",\"tag\":\"service.booking\",\"lineno\":39,\"function\":\"xxx.xx/service-booking/internal/rpc/booking.(*Handler).Handle.func1\",\"run\":\"578710a04dbb\",\"comp\":\"rpc.booking\",\"payload_resp\":\"{\\\"provider\\\":\\\"None\\\"}\",\"payload_req\":\"{\\\"userId\\\":34664834}\",\"xsource\":\"profile\",\"_tracing\":{\"uber-trace-id\":\"669f:6a2c:c35c:1\"}}\n```\n\nIt turns to:\n\n```\nI 07:33:44 5787 rpc.booking xxx.xx/service-booking/internal/rpc/booking.(*Handler).Handle.func1 39 RPC call payload_req={\"userId\":34664834} payload_resp={\"provider\":\"None\"}\n```\n\n## One more example: settings for gRPC+slog out of the box logging\n\nBasic settings for [github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/logging](https://github.com/grpc-ecosystem/go-grpc-middleware/) logs.\n\n```\n# file: pplog.env\n\nPPLOG_LOGLINE='\n{{- .time | tmf \"2006-01-02T15:04:05Z07:00\" \"15:04:05\" }}{{\" \"}}\n{{- if .level }}\n  {{- if eq .level \"DEBUG\"}}\\e[90m\n  {{- else if eq .level \"INFO\" }}\\e[32m\n  {{- else}}\\e[91m\n  {{- end }}\n  {{- .level }}\\e[0m\n{{- end }}{{\" \"}}\n{{- if (index . \"grpc.code\") }}\n  {{- if eq \"OK\" (index . \"grpc.code\") }}\\e[32mOK\\e[0m {{else}}\\e[91m{{ index . \"grpc.code\" }}\\e[0m {{ end }}\n{{- else -}}\n  {{\"- \"}}\n{{- end -}}\n\\e[35m{{ index . \"grpc.component\" }}/\\e[95m{{ index . \"grpc.service\" }}\\e[35m/{{ index . \"grpc.method\" }}\\e[0m{{\" \"}}\n{{- .msg }}\n{{- range .ALL | rm \"msg\" \"time\" \"level\" \"grpc.component\" \"grpc.service\" \"grpc.method\" \"grpc.code\"}} \\e[33m{{.K}}\\e[0m={{.V}}{{end}}'\n\nPPLOG_ERRLINE='{{ if .BINARY }}{{ .TEXT }}{{ else }}\\e[97m{{.TEXT}}\\e[0m{{ end }}'\n```\n\n## Step by step customization\n\nFirst things first, I recommend you to prepare small file with your logs. Let's name it `example.log`.\n\nNow you can start to play with it by command like that:\n\n```sh\npplog cat example.log\n```\n\nYou will see some formatted logs.\n\nNow create your first `pplog.env`. You can start from this universal one:\n\n```sh\nPPLOG_LOGLINE='{{range .ALL}}{{.K}}={{.V}} {{end}}'\nPPLOG_ERRLINE='Invalid JONS: {{.TEXT}}'\n```\n\nYou will see all your logs in KEY=VALUE format. Now look over all your keys and choose one you want\nto see in the first place. Say, `message`. Modify your `pplog.env` this way:\n\n```sh\nPPLOG_LOGLINE='{{.message}}{{range .ALL | rm \"message\"}} {{.K}}={{.V}}{{end}}'\nPPLOG_ERRLINE='Invalid JONS: {{.TEXT}}'\n```\n\nYou will see `message` in the first place and remove it from KEY=VALUE tail.\n\nNow, you are free to add colors:\n\n```sh\nPPLOG_LOGLINE='\\e[32m{{.message}}\\e[m{{range .ALL | rm \"message\"}} {{.K}}={{.V}}{{end}}'\nPPLOG_ERRLINE='Invalid JONS: {{.TEXT}}'\n```\n\nWe makes `message` green. Keep shaping your logs field by field.\n\n## Template functions\n\n- All [`Masterminds/sprig/v3` functions](https://masterminds.github.io/sprig/)\n- `trimSpaces` — example: `PPLOG_ERRLINE='INVALID: {{ .TEXT | trimSpace | printf \"%q\" }}'`\n- `tmf` — example: `{{ .A | tmf \"2006-01-02T15:04:05Z07:00\" \"15:04:05\" }}`\n- `rm` — example: `{{ range .ALL | rm \"A\" \"B\" \"C\" }}{{.K}}={{.V}};{{end}}`\n- `rmByPfx`\n- `xjson`\n- `xxjson` (experimental)\n\n## Template special variables\n\n- In `PPLOG_LOGLINE` template:\n    - `RAW_INPUT`\n    - `ALL` — list of all pairs key-value\n- In `PPLOG_ERRLINE` template:\n    - `TEXT`\n    - `BINARY` — does TEXT contains control characters\n- If `PPLOG_CHILD_MODE` not empty `pplog` runs in child mode as if it has `-c` switch\n\n## Most common colors\n\n```\nText colors          Text High            Background           Hi Background         Decoration\n------------------   ------------------   ------------------   -------------------   --------------------\n\\e[30mBlack  \\e[0m   \\e[90mBlack  \\e[0m   \\e[40mBlack  \\e[0m   \\e[100mBlack  \\e[0m   \\e[1mBold      \\e[0m\n\\e[31mRed    \\e[0m   \\e[91mRed    \\e[0m   \\e[41mRed    \\e[0m   \\e[101mRed    \\e[0m   \\e[4mUnderline \\e[0m\n\\e[32mGreen  \\e[0m   \\e[92mGreen  \\e[0m   \\e[42mGreen  \\e[0m   \\e[102mGreen  \\e[0m   \\e[7mReverse   \\e[0m\n\\e[33mYellow \\e[0m   \\e[93mYellow \\e[0m   \\e[43mYellow \\e[0m   \\e[103mYellow \\e[0m\n\\e[34mBlue   \\e[0m   \\e[94mBlue   \\e[0m   \\e[44mBlue   \\e[0m   \\e[104mBlue   \\e[0m   Combinations\n\\e[35mMagenta\\e[0m   \\e[95mMagenta\\e[0m   \\e[45mMagenta\\e[0m   \\e[105mMagenta\\e[0m   -----------------------\n\\e[36mCyan   \\e[0m   \\e[96mCyan   \\e[0m   \\e[46mCyan   \\e[0m   \\e[106mCyan   \\e[0m   \\e[1;4;103;31mWARN\\e[0m\n\\e[37mWhite  \\e[0m   \\e[97mWhite  \\e[0m   \\e[47mWhite  \\e[0m   \\e[107mWhite  \\e[0m\n```\n\n## Run modes explanation\n\n### Pipe mode\n\nThe most confident mode. In this mode your shell cares about all your processes. Just do\n\n```sh\n./service | pplog\n# or with redirections if you need to take both stderr and stdout\n./service 2\u003e\u00261 | pplog\n# or the same redirections in modern shells\n./service |\u0026 pplog\n```\n\n### Simple subprocess mode\n\nIf you say just like that:\n\n```sh\npplog ./service\n```\n\n`pplog` runs `./servcie` as a child process and tries to manage it.\n\nIf you press Ctrl-C, `pplog` sends `SIGINT`, `SIGTERM`, `SIGKILL` to its child consequently with 1s delay in between.\n\n`pplog` tries to wait child process exited and returns its exit code transparently.\n\nObvious disadvantage is that `pplog` doesn't try to manage children of child (if any), daemons etc.\n\n### Child (or coprocess) mode\n\nIn this mode `pplog` starts as a child of `./service`\n\n```sh\npplog -c ./service\n```\n\nSo, `./service` itself obtains all signals and Ctrl-Cs directly.\n\nHowever, there are disadvantages here too. `pplog` can not get `./service`s exit code. And this mode unavailable under MS Windows.\n\n## Similar projects\n\n- `jlv` (JSON Log Viewer) — [https://github.com/hedhyw/json-log-viewer](https://github.com/hedhyw/json-log-viewer)\n- `logdy` — [https://logdy.dev/](https://logdy.dev/)\n- `humanlog` — [https://humanlog.io/](https://humanlog.io/), [https://github.com/humanlogio/humanlog](https://github.com/humanlogio/humanlog)\n- `jq` — `echo '{\"time\":\"12:00\",\"msg\":\"OK\"}' | jq -r '.time+\" \"+.msg'` produces `12:00 OK` — [https://jqlang.github.io/jq/](https://jqlang.github.io/jq/)\n\n## TODO\n\n- Usage: show templates in debug mode\n- Behavior tests:\n    - `-c`\n    - `PPLOG_CHILD_MODE` environment variable\n    - basic `runs-on: windows-latest`\n    - passing exit code\n- Docs: contributing guide: how to run behavior tests locally\n- Docs: godoc\n\n## Known issues\n\n### Not optimal integration with log/slog\n\nIf you decided to use this code as library as part of your product, you have to keep in mind, that\nthis tool provides `io.Writer` to pipe log stream. It is easiest way to modify behavior of logger, however\nit leads to overhead for extra marshaling/unmarshaling. However, as well as we use human readable logs in\nlocal environment only, it is acceptable to have a little overhead.\n\n### Subprocesses handling issues\n\nThe problem is that many processes have to be synchronized: shell-process, pplog-process, target-process with all its children.\n\nYou are able to choose one of three modes: pipe-, subprocess- and child-mode. Each of them has its own disadvantages.\n\n### Line-by-line processing\n\nThis tool processes input stream line by line. It means, that it won't work with multiline JSONs in logs like that\n\n```json\n{\n  \"level\": \"info\",\n  \"message\": \"obtaining data\"\n}\n{\n  \"level\": \"error\",\n  \"message\": \"invalid data\"\n}\n```\n\nas well as it won't work with mixed lines like this:\n\n```\nRaw message{\"message\": \"valid json log record\"}\n```\n\nall that cases will be considered and output as wrong JSON.\n\nHonestly, I have tried to implement smarter scanner. It's not a big deal, however,\nin fact, it is not convenient. For instance, it consider message like this `code=200`\nin wearied way: `code=` is wrong JSON, however `200` is valid JSON.\nThings like that `0xc00016f0e1` get really awful.\n\nI have played with different approaches\nand decided just to split logs line by line first.\n\n### Hard to reproduce issue\n\nIt seems there is a problem appears in subprocess mode when subprocess going to die and its final\noutput makes error (or panic?) in `text/template` package.\n\nThis issue has to be solved by [this commit](https://github.com/michurin/human-readable-json-logging/commit/c8ce47a67812e8f616b0c23a7b1abc2fced15461),\nhowever please report if you find how to reproduce such things.\n\n## Contributors\n\n- [vitalyshatskikh](https://github.com/vitalyshatskikh)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmichurin%2Fhuman-readable-json-logging","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmichurin%2Fhuman-readable-json-logging","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmichurin%2Fhuman-readable-json-logging/lists"}