{"id":38053727,"url":"https://github.com/40ants/log4cl-extras","last_synced_at":"2026-01-16T20:20:27.576Z","repository":{"id":46620100,"uuid":"256944800","full_name":"40ants/log4cl-extras","owner":"40ants","description":"A few addons for log4cl Common Lisp logging library.","archived":false,"fork":false,"pushed_at":"2025-12-01T13:35:58.000Z","size":374,"stargazers_count":14,"open_issues_count":3,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-12-04T02:22:27.712Z","etag":null,"topics":["common-lisp","logging"],"latest_commit_sha":null,"homepage":"https://40ants.com/log4cl-extras/","language":"Common Lisp","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/40ants.png","metadata":{"files":{"readme":"README.md","changelog":"ChangeLog.md","contributing":null,"funding":null,"license":null,"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,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2020-04-19T07:56:59.000Z","updated_at":"2025-10-05T20:26:05.000Z","dependencies_parsed_at":"2023-11-19T15:30:06.264Z","dependency_job_id":"821243e2-397f-402f-9716-edd030aa66e5","html_url":"https://github.com/40ants/log4cl-extras","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/40ants/log4cl-extras","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/40ants%2Flog4cl-extras","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/40ants%2Flog4cl-extras/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/40ants%2Flog4cl-extras/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/40ants%2Flog4cl-extras/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/40ants","download_url":"https://codeload.github.com/40ants/log4cl-extras/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/40ants%2Flog4cl-extras/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28482267,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-16T11:59:17.896Z","status":"ssl_error","status_checked_at":"2026-01-16T11:55:55.838Z","response_time":107,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["common-lisp","logging"],"created_at":"2026-01-16T20:20:25.100Z","updated_at":"2026-01-16T20:20:27.568Z","avatar_url":"https://github.com/40ants.png","language":"Common Lisp","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ca id=\"x-28LOG4CL-EXTRAS-2FDOC-3A-40README-2040ANTS-DOC-2FLOCATIVES-3ASECTION-29\"\u003e\u003c/a\u003e\n\n# LOG4CL-EXTRAS - Addons for Log4CL\n\n\u003ca id=\"log4-cl-extras-asdf-system-details\"\u003e\u003c/a\u003e\n\n## LOG4CL-EXTRAS ASDF System Details\n\n* Description: A bunch of addons to `LOG4CL`: `JSON` appender, context fields, cross-finger appender, etc.\n* Licence: `BSD`\n* Author: Alexander Artemenko\n* Homepage: [https://40ants.com/log4cl-extras/][2906]\n* Bug tracker: [https://github.com/40ants/log4cl-extras/issues][d7d4]\n* Source control: [GIT][8f00]\n* Depends on: [40ants-doc][2c00], [alexandria][8236], [cl-strings][2ecb], [dissect][70a8], [global-vars][07be], [jonathan][6dd8], [local-time][46a1], [log4cl][7f8b], [named-readtables][d0a9], [pythonic-string-reader][c01d], [with-output-to-stream][9201]\n\n[![](https://github-actions.40ants.com/40ants/log4cl-extras/matrix.svg?only=ci.run-tests)][b509]\n\n\u003ca id=\"x-28LOG4CL-EXTRAS-2FDOC-3A-3A-40INSTALLATION-2040ANTS-DOC-2FLOCATIVES-3ASECTION-29\"\u003e\u003c/a\u003e\n\n## Installation\n\nYou can install this library from Quicklisp, but you want to receive updates quickly, then install it from Ultralisp.org:\n\n```\n(ql-dist:install-dist \"http://dist.ultralisp.org/\"\n                      :prompt nil)\n(ql:quickload :log4cl-extras)\n```\n\u003ca id=\"x-28LOG4CL-EXTRAS-2FCONFIG-3A-3A-40CONFIGURATION-2040ANTS-DOC-2FLOCATIVES-3ASECTION-29\"\u003e\u003c/a\u003e\n\n## Configuration\n\nBy default `LOG4CL` outputs log items like this:\n\n```\nCL-USER\u003e (log:info \"Hello\")\n \u003cINFO\u003e [01:15:37] cl-user () - Hello\n```\nHowever logging extra context fields requires a custom \"layout\".\nLayout defines the way how the message will be formatted.\n\nThis system defines two layout types:\n\n* `:PLAIN` - a layout for printing messages to the `REPL`.\n* `:JSON` - a layout which outputs each message and all it's data\n  as a `JSON` documents. Use it to feed logs to Elastic Search\n  or a service like [Datadog][646b]\n  to [Papertrail][ee55].\n\nTo use these custom layouts, you have to use [`setup`][74de] function. It also allows to set a log level\nfor root logger and appenders. Here is a minimal example showing how to configure logger for the `REPL`:\n\n```\nCL-USER\u003e (log4cl-extras/config:setup\n          '(:level :debug\n            :appenders ((this-console :layout :plain))))\nNIL\n\nCL-USER\u003e (log:info \"Hello\")\n\u003cINFO\u003e [2021-05-16T01:16:46.978992+03:00] Hello\n\nCL-USER\u003e (log4cl-extras/context:with-fields (:foo \"Bar\")\n           (log:info \"Hello\"))\n\u003cINFO\u003e [2021-05-16T01:26:30.331849+03:00] Hello\n  Fields:\n    foo: Bar\n```\nIf you replace `:PLAIN` with `:JSON`, you'll get this:\n\n```\nCL-USER\u003e (log4cl-extras/config:setup\n          '(:level :debug\n            :appenders ((this-console :layout :json))))\n; No values\nCL-USER\u003e (log4cl-extras/context:with-fields (:foo \"Bar\")\n           (log:info \"Hello\"))\n{\"fields\":{\"foo\":\"Bar\"},\"level\":\"INFO\",\"message\":\"Hello\",\"timestamp\":\"2021-05-16T01:27:54.879336+03:00\"}\n```\nAppender in terms of log4cl is a target for log output. You can combine multiple appenders\nin a single call to [`setup`][74de] function.\n\nHere is the example of the config, suitable for production. Here we log all messages as `JSON` records\ninto the file, rotated on the daily basis. And all errors and warnings will be written to the `REPL`.\n\n```\n(log4cl-extras/config:setup\n '(:level :debug\n   :appenders ((this-console :layout :plain\n                             :filter :warn)\n               (daily :layout :json\n                      :name-format \"/app/logs/app.log\"\n                      :backup-name-format \"app-%Y%m%d.log\"))))\n```\nAlso, [`setup`][74de] allows to change log levels for different loggers:\n\n\u003ca id=\"x-28LOG4CL-EXTRAS-2FCONFIG-3ASETUP-20FUNCTION-29\"\u003e\u003c/a\u003e\n\n### [function](afb7) `log4cl-extras/config:setup` config\n\nSetup loggers and appenders via confg.\n\nExample:\n\n```\n(setup\n '(:level :error\n   :appenders\n   (this-console\n    (file\n     :file \"foo.log\"\n     :layout json))\n   :loggers\n   ((log4cl-extras/config\n     :loggers\n     ((foo :level :debug)\n      (bar\n       :loggers\n       ((some-class\n         :level debug))))))))\n```\nAs you can see, [`setup`][74de] function accepts a plist with keys `:LEVEL`, `:APPENDERS` and `:LOGGERS`.\n\n`:LEVEL` key holds a logging level for the root logger. It could be `:INFO`, `:WARN` or `:ERROR`.\n\n`:APPENDERS` is a list of list where each sublist should start from the appender type and arguments for it's constructor.\n\nSupported appenders are:\n\n* `THIS-CONSOLE` corresponds to `LOG4CL:THIS-CONSOLE-APPENDER` class.\n* `DAILY` corresponds to `LOG4CL:DAILY-FILE-APPENDER` class.\n* `FILE` corresponds to `LOG4CL:FILE-APPENDER`.\n\nTo lookup supported arguments for each appender type, see these classes initargs.\nthe only difference is that `:LAYOUT` argument is processed in a special way:\n`:JSON` value replaced with [`log4cl-extras/json:json-layout`][f356] and `:PLAIN` is replaced\nwith [`log4cl-extras/plain:plain-layout`][f2d0].\n\nAnd finally, you can pass to [`setup`][74de] a list of loggers. Each item in this list\nshould be plist where first item is a symbolic name of a package or a function name\ninside a package and other items are params for a nested [`setup`][74de] call.\n\n\u003ca id=\"layouts\"\u003e\u003c/a\u003e\n\n### Layouts\n\n\u003ca id=\"x-28LOG4CL-EXTRAS-2FPLAIN-3APLAIN-LAYOUT-20CLASS-29\"\u003e\u003c/a\u003e\n\n### [class](1ba4) `log4cl-extras/plain:plain-layout` (layout)\n\n\u003ca id=\"x-28LOG4CL-EXTRAS-2FJSON-3AJSON-LAYOUT-20CLASS-29\"\u003e\u003c/a\u003e\n\n### [class](1a91) `log4cl-extras/json:json-layout` (layout)\n\n\u003ca id=\"x-28LOG4CL-EXTRAS-2FCONTEXT-3A-3A-40CONTEXT-2040ANTS-DOC-2FLOCATIVES-3ASECTION-29\"\u003e\u003c/a\u003e\n\n## Context Fields\n\nMacro [`with-fields`][b464] let to to capture some information into the dynamic variable.\nAll messages logged inside the [`with-fields`][b464] form will have these fields attached:\n\n```\nCL-USER\u003e (log4cl-extras/config:setup\n          '(:level :debug\n            :appenders ((this-console :layout :plain))))\n\nCL-USER\u003e (defun process-request ()\n           (log:info \"Processing request\"))\n\nCL-USER\u003e (log4cl-extras/context:with-fields (:request-id 42)\n           (process-request))\n\u003cINFO\u003e [2020-07-19T10:03:21.079636Z] Processing request\n  Fields:\n    request-id: 42\n\n;; Now let's switch to JSON layout:\nCL-USER\u003e (log4cl-extras/config:setup\n          '(:level :debug\n            :appenders ((this-console :layout :json))))\n\nCL-USER\u003e (log4cl-extras/context:with-fields (:request-id 42)\n           (process-request))\n{\"fields\": {\"request-id\": 42},\n \"level\": \"INFO\",\n \"message\": \"Processing request\",\n \"timestamp\": \"2020-07-19T10:03:32.855593Z\"}\n```\n**Beware!**, catching context fields costs some time even if they are not logged.\n\n\u003ca id=\"x-28LOG4CL-EXTRAS-2FCONTEXT-3AWITH-FIELDS-20-2840ANTS-DOC-2FLOCATIVES-3AMACRO-29-29\"\u003e\u003c/a\u003e\n\n### [macro](b23c) `log4cl-extras/context:with-fields` (\u0026rest fields) \u0026body body\n\nCaptures content of given fields into a dynamic variable.\n\nThese fields will be logged along with any log entry\ninside the [`with-fields`][b464] body.\n\n\u003ca id=\"x-28LOG4CL-EXTRAS-2FCONTEXT-3AGET-FIELDS-20FUNCTION-29\"\u003e\u003c/a\u003e\n\n### [function](6b38) `log4cl-extras/context:get-fields`\n\nReturns an alist of all fields defined using [`with-fields`][b464] macro in the current stack.\n\nKeys are returned as downcased strings, prepared for logging.\n\n\u003ca id=\"x-28LOG4CL-EXTRAS-2FERROR-3A-3A-40ERRORS-2040ANTS-DOC-2FLOCATIVES-3ASECTION-29\"\u003e\u003c/a\u003e\n\n## Logging Unhandled Errors\n\n\u003ca id=\"x-28LOG4CL-EXTRAS-2FERROR-3A-3A-40INTRO-2040ANTS-DOC-2FLOCATIVES-3ASECTION-29\"\u003e\u003c/a\u003e\n\n### Quickstart\n\nIf you want to log unhandled signals traceback, then use [`with-log-unhandled`][3fd6] macro.\n\nUsually it is good idea, to use [`with-log-unhandled`][3fd6] in the main function or in a function which handles\na `HTTP` request.\n\nIf some error condition will be signaled by the body, it will be logged as an error with \"traceback\"\nfield like this:\n\n```\nCL-USER\u003e (defun foo ()\n           (error \"Some error happened\"))\n\nCL-USER\u003e (defun bar ()\n           (foo))\n\nCL-USER\u003e (log4cl-extras/error:with-log-unhandled ()\n           (bar))\n\n\u003cERROR\u003e [2020-07-19T10:14:39.644805Z] Unhandled exception\n  Fields:\n  Traceback (most recent call last):\n    File \"NIL\", line NIL, in FOO\n      (FOO)\n    File \"NIL\", line NIL, in BAR\n      (BAR)\n    File \"NIL\", line NIL, in (LAMBDA (…\n      ((LAMBDA ()))\n    File \"NIL\", line NIL, in SIMPLE-EV…\n      (SB-INT:SIMPLE-EVAL-IN-LEXENV\n       (LOG4CL-EXTRAS/ERROR:WITH-LOG-UNHANDLED NIL\n         (BAR))\n       #\u003cNULL-LEXENV\u003e)\n    ...\n       #\u003cCLOSURE (LAMBDA () :IN SLYNK::CALL-WITH-LISTENER) {100A6B043B}\u003e)\n     \n     \n  Condition: Some error happened\n; Debugger entered on #\u003cSIMPLE-ERROR \"Some error happened\" {100A7A5DB3}\u003e\n```\nThe `JSON` layout will write such error like this:\n\n```\n{\n  \"fields\": {\n    \"traceback\": \"Traceback (most recent call last):\\n  File \\\"NIL\\\", line NIL, in FOO\\n    (FOO)\\n  File \\\"NIL\\\", line NIL, in BAR\\n    (BAR)\\n...\\nCondition: Some error happened\"\n  },\n  \"level\": \"ERROR\",\n  \"message\": \"Unhandled exception\",\n  \"timestamp\": \"2020-07-19T10:21:33.557418Z\"\n}\n```\n\u003ca id=\"x-28LOG4CL-EXTRAS-2FERROR-3A-3A-40PRINTING-2040ANTS-DOC-2FLOCATIVES-3ASECTION-29\"\u003e\u003c/a\u003e\n\n### Printing Backtrace\n\nThere is a helper function [`print-backtrace`][6a57] for extracting and printing backtrace, which can be used\nseparately from logging. One use case is to render backtrace on the web page when a\nsite is in a debug mode:\n\n```\nCL-USER\u003e (log4cl-extras/error:print-backtrace :depth 3)\nTraceback (most recent call last):\n   0 File \"/Users/art/.roswell/src/sbcl-2.0.11/src/code/eval.lisp\", line 291\n       In SB-INT:SIMPLE-EVAL-IN-LEXENV\n     Args ((LOG4CL-EXTRAS/ERROR:PRINT-BACKTRACE :DEPTH 3) #\u003cNULL-LEXENV\u003e)\n   1 File \"/Users/art/.roswell/src/sbcl-2.0.11/src/code/eval.lisp\", line 311\n       In EVAL\n     Args ((LOG4CL-EXTRAS/ERROR:PRINT-BACKTRACE :DEPTH 3))\n   2 File \"/Users/art/projects/lisp/sly/contrib/slynk-mrepl.lisp\"\n       In (LAMBDA () :IN SLYNK-MREPL::MREPL-EVAL-1)\n     Args ()\n```\nBy default, it prints to the `*DEBUG-IO*`, but you can pass it a `:STREAM` argument\nwhich has the same semantic as a stream for `FORMAT` function.\n\nOther useful parameters are `:DEPTH` and `:MAX-CALL-LENGTH`. They allow to control how\nlong and wide backtrace will be.\n\nAlso, you might pass `:CONDITION`. If it is given, it will be printed after the backtrace.\n\nAnd finally, you can pass a list of functions to filter arguments before printing.\nThis way secret or unnecesary long values can be stripped. See the next section to learn\nhow to not log secret values.\n\n\u003ca id=\"api\"\u003e\u003c/a\u003e\n\n### API\n\n\u003ca id=\"x-28LOG4CL-EXTRAS-2FERROR-3A-2AMAX-TRACEBACK-DEPTH-2A-20-28VARIABLE-29-29\"\u003e\u003c/a\u003e\n\n### [variable](d588) `log4cl-extras/error:*max-traceback-depth*` 10\n\nKeeps default value for traceback depth logged by [`with-log-unhandled`][3fd6] macro\n\n\u003ca id=\"x-28LOG4CL-EXTRAS-2FERROR-3A-2AMAX-CALL-LENGTH-2A-20-28VARIABLE-29-29\"\u003e\u003c/a\u003e\n\n### [variable](616b) `log4cl-extras/error:*max-call-length*` 100\n\nThe max length of each line in a traceback. It is useful to limit it because otherwise some log collectors can discard the whole log entry.\n\n\u003ca id=\"x-28LOG4CL-EXTRAS-2FERROR-3A-2AARGS-FILTERS-2A-20-28VARIABLE-29-29\"\u003e\u003c/a\u003e\n\n### [variable](56cd) `log4cl-extras/error:*args-filters*` nil\n\nAdd to this variable functions of two arguments to change arguments before they will be dumped\nas part of the backtrace to the log.\n\nThis is not a special variable, because it should be changed system-wide and can be accessedd\nfrom multiple threads.\n\n\u003ca id=\"x-28LOG4CL-EXTRAS-2FERROR-3A-2AARGS-FILTER-CONSTRUCTORS-2A-20-28VARIABLE-29-29\"\u003e\u003c/a\u003e\n\n### [variable](7d10) `log4cl-extras/error:*args-filter-constructors*` nil\n\nAdd to this variable functions of zero arguments. Each function should return an argument filter\nfunction suitable for using in the [`*args-filters*`][c7a0] variable.\n\nThese constructors can be used to create argument filters with state suitable for\nprocessing of a single backtrace only. For example,\n[`log4cl-extras/secrets:make-secrets-replacer`][bb11] function, keeps tracks every secret value used in\nall frames of the backtrace. We don't want to keep these values forever and to mix secrets\nof different users in the same place. Thus this function should be used as a \"constructor\".\nIn this case it will create a new secret replacer for every backtrace to be processed.\n\n\u003ca id=\"x-28LOG4CL-EXTRAS-2FERROR-3AWITH-LOG-UNHANDLED-20-2840ANTS-DOC-2FLOCATIVES-3AMACRO-29-29\"\u003e\u003c/a\u003e\n\n### [macro](176d) `log4cl-extras/error:with-log-unhandled` (\u0026key (depth \\*max-traceback-depth\\*) (errors-to-ignore nil)) \u0026body body\n\nLogs any `ERROR` condition signaled from the body. Logged message will have a \"traceback\" field.\n\nYou may specify a list of error classes to ignore as `ERRORS-TO-IGNORE` argument.\nErrors matching (typep err \u003ceach-of errors-to-ignore\u003e) will not be logged as \"Unhandled\".\n\n\u003ca id=\"x-28LOG4CL-EXTRAS-2FERROR-3APRINT-BACKTRACE-20FUNCTION-29\"\u003e\u003c/a\u003e\n\n### [function](5328) `log4cl-extras/error:print-backtrace` \u0026key (stream \\*debug-io\\*) (condition nil) (depth \\*max-traceback-depth\\*) (max-call-length \\*max-call-length\\*) (args-filters (get-current-args-filters)) (format-condition #'format-condition-object)\n\nA helper to print backtrace. Could be useful to out backtrace\nat places other than logs, for example at a web page.\n\nThis function applies the same filtering rules as [`with-log-unhandled`][3fd6] macro.\n\nBy default condition description is printed like this:\n\n```\nCondition REBLOCKS-WEBSOCKET:NO-ACTIVE-WEBSOCKETS: No active websockets bound to the current page.\n```\nBut you can change this by providing an argument `FORMAT-CONDITION`. It should be a\nfunction of two arguments: `(stream condition)`.\n\n\u003ca id=\"x-28LOG4CL-EXTRAS-2FERROR-3AMAKE-ARGS-FILTER-20FUNCTION-29\"\u003e\u003c/a\u003e\n\n### [function](ce37) `log4cl-extras/error:make-args-filter` predicate placeholder\n\nReturns a function, suitable to be used in [`*args-filters*`][c7a0] variable.\n\nFunction `PREDICATE` will be applied to each argument in the frame\nand if it returns T, then argument will be replaced with `PLACEHOLDER`.\n\n\u003ca id=\"x-28LOG4CL-EXTRAS-2FERROR-3APLACEHOLDER-20CLASS-29\"\u003e\u003c/a\u003e\n\n### [class](00a5) `log4cl-extras/error:placeholder` ()\n\nObjects of this class can be used as replacement to arguments in a backtrace.\n\nThey are printed like `#\u003csome-name\u003e`.\n\nThis form was choosen to match the way how `SBCL` shows unused arguments: `#\u003cunused argument\u003e`.\n\nPlaceholders should be created with [`make-placeholder`][de65] function.\n\n\u003ca id=\"x-28LOG4CL-EXTRAS-2FERROR-3AMAKE-PLACEHOLDER-20FUNCTION-29\"\u003e\u003c/a\u003e\n\n### [function](d802) `log4cl-extras/error:make-placeholder` name\n\nCreates a placeholder for some secret value or omitted argument.\n\n```\nCL-USER\u003e (log4cl-extras/error:make-placeholder \"secret value\")\n\n#\u003csecret value\u003e\n```\nSee [`Hard Way`][2087] section to learn, how to use\nplaceholders to remove sensitive information from logs.\n\n\u003ca id=\"x-28LOG4CL-EXTRAS-2FERROR-3APLACEHOLDER-P-20FUNCTION-29\"\u003e\u003c/a\u003e\n\n### [function](c537) `log4cl-extras/error:placeholder-p` obj\n\n\u003ca id=\"x-28LOG4CL-EXTRAS-2FERROR-3APLACEHOLDER-NAME-20-2840ANTS-DOC-2FLOCATIVES-3AREADER-20LOG4CL-EXTRAS-2FERROR-3APLACEHOLDER-29-29\"\u003e\u003c/a\u003e\n\n### [reader](4e5b) `log4cl-extras/error:placeholder-name` (placeholder) (:name)\n\n\u003ca id=\"x-28LOG4CL-EXTRAS-2FSECRETS-3A-3A-40KEEPING-SECRETS-2040ANTS-DOC-2FLOCATIVES-3ASECTION-29\"\u003e\u003c/a\u003e\n\n## How Keep Secrets Out of Logs\n\nWhen backtrace is printed to log files it is good idea to omit passwords, tokens, cookies,\nand other potentially sensitive values.\n\nHere is a potential situation where you have a password and trying to create a new connection\nto the database. But because of some network error, an unhandled error along with a backtrace\nwill be logged. Pay attention to our secret password in the log:\n\n```\nCL-USER\u003e (log4cl-extras/config:setup\n           '(:level :error\n             :appenders ((this-console :layout plain))))\n   \nCL-USER\u003e (defun connect (password)\n           \"Normally, we don't control this function's code\n  because it is from the third-party library.\"\n           (check-type password string)\n           (error \"Network timeout\"))\n   \nCL-USER\u003e (defun authenticate (password)\n           \"This function is in our app's codebase.\n  It is calling a third-party DB driver.\"     \n           (connect password))\n   \nCL-USER\u003e (defun bar (password)\n           (authenticate password))\n   \nCL-USER\u003e (log4cl-extras/error:with-log-unhandled (:depth 5)\n           (bar \"The Secret Password\"))\n\u003cERROR\u003e [2021-01-24T14:13:24.460890+03:00] Unhandled exception\n  Fields:\n  Traceback (most recent call last):\n     0 File \"unknown\"\n         In (FLET \"H0\")\n       Args (#\u003cSIMPLE-ERROR \"Network timeout\" {100F065533}\u003e)\n     1 File \"/Users/art/.roswell/src/sbcl-2.0.11/src/code/cold-error.lisp\", line 81\n         In SB-KERNEL::%SIGNAL\n       Args (#\u003cSIMPLE-ERROR \"Network timeout\" {100F065533}\u003e)\n     2 File \"/Users/art/.roswell/src/sbcl-2.0.11/src/code/cold-error.lisp\", line 154\n         In ERROR\n       Args (\"Network timeout\")\n     3 File \"unknown\"\n         In CONNECT\n       Args (\"The Secret Password\")\n     4 File \"unknown\"\n         In AUTHENTICATE\n       Args (\"The Secret Password\")\n  \n  Condition: Network timeout\n```\nWith [`log4cl-extras`][1395] you can keep values in secret in two ways.\n\n\u003ca id=\"x-28LOG4CL-EXTRAS-2FSECRETS-3A-3A-40EASY-WAY-2040ANTS-DOC-2FLOCATIVES-3ASECTION-29\"\u003e\u003c/a\u003e\n\n### Easy Way\n\nThe easiest way, is to wrap all sensitive data using\n[secret-values][ee75]\nlibrary as soon as possible and unwrap them only before usage.\n\nLets see what will happen if we'll use a wrapped password.\n\nFirst, we need to teach `AUTHENTICATE` function, how to unwrap\nthe password, before passing it to the driver:\n\n```\nCL-USER\u003e (defun authenticate (password)\n           \"This function is in our app's codebase.\n  It is calling a third-party DB driver.\"\n           (connect\n            (secret-values:ensure-value-revealed\n             password)))\n```\nNext, we need to wrap password into a special object. It is better to\ndo this as soon as possible. In production code you'll probably have\nsomething like `(secret-values:conceal-value (uiop:getenv \"POSTGRES_PASSWORD\"))`:\n\n```\nCL-USER\u003e (log4cl-extras/error:with-log-unhandled (:depth 5)\n           (bar (secret-values:conceal-value\n                 \"The Secret Password\")))\n\u003cERROR\u003e [2021-01-24T14:16:01.667651+03:00] Unhandled exception\n  Fields:\n  Traceback (most recent call last):\n     0 File \"unknown\"\n         In (FLET \"H0\")\n       Args (#\u003cSIMPLE-ERROR \"Network timeout\" {10036CB1A3}\u003e)\n     1 File \"/Users/art/.roswell/src/sbcl-2.0.11/src/code/cold-error.lisp\", line 81\n         In SB-KERNEL::%SIGNAL\n       Args (#\u003cSIMPLE-ERROR \"Network timeout\" {10036CB1A3}\u003e)\n     2 File \"/Users/art/.roswell/src/sbcl-2.0.11/src/code/cold-error.lisp\", line 154\n         In ERROR\n       Args (\"Network timeout\")\n     3 File \"unknown\"\n         In CONNECT\n       Args (\"The Secret Password\")\n     4 File \"unknown\"\n         In AUTHENTICATE\n       Args (#\u003cSECRET-VALUES:SECRET-VALUE {10036CB183}\u003e)\n     \n  Condition: Network timeout\n```\nPay attention to the fourth stack frame. `AUTHENTICATE` function has\n`#\u003cSECRET-VALUES:SECRET-VALUE {10036CB183}\u003e` as the first argument.\nBut why do we see `\"The Secret Password\"` in the third frame anyway?\n\nIt is because we have to pass a raw version of the password to the libraries\nwe don't control.\n\nHere is where [`log4cl-extras`][1395] comes to the resque. It provides a package\n`LOG4CL-EXTRAS/SECRETS`. It is optional and is not loaded together with the\nprimary system.\n\nEarlier, I've mentioned `:ARGS-FILTERS` argument to the [`log4cl-extras/error:print-backtrace`][6a57] function.\nPackage `LOG4CL-EXTRAS/SECRETS` provides a function [`make-secrets-replacer`][bb11]\nwhich can be used to filter secret values.\n\nWe can add it into the global variable [`log4cl-extras/error:*args-filter-constructors*`][5c08] like this:\n\n```\nCL-USER\u003e (ql:quickload :log4cl-extras/secrets)\n(:LOG4CL-EXTRAS/SECRETS)\n   \nCL-USER\u003e (setf log4cl-extras/error:*args-filter-constructors*\n               (list 'log4cl-extras/secrets:make-secrets-replacer))\n(#\u003cFUNCTION LOG4CL-EXTRAS/SECRETS:MAKE-SECRETS-REPLACER\u003e)\n```\nNow let's try to connect to our fake database again:\n\n```\nCL-USER\u003e (log4cl-extras/error:with-log-unhandled (:depth 5)\n           (bar (secret-values:conceal-value\n                 \"The Secret Password\")))\n\u003cERROR\u003e [2021-01-24T14:27:17.851716+03:00] Unhandled exception\n  Fields:\n  Traceback (most recent call last):\n     0 File \"unknown\"\n         In (FLET \"H0\")\n       Args (#\u003cSIMPLE-ERROR \"Network timeout\" {100800F723}\u003e)\n     1 File \"/Users/art/.roswell/src/sbcl-2.0.11/src/code/cold-error.lisp\", line 81\n         In SB-KERNEL::%SIGNAL\n       Args (#\u003cSIMPLE-ERROR \"Network timeout\" {100800F723}\u003e)\n     2 File \"/Users/art/.roswell/src/sbcl-2.0.11/src/code/cold-error.lisp\", line 154\n         In ERROR\n       Args (\"Network timeout\")\n     3 File \"unknown\"\n         In CONNECT\n       Args (#\u003csecret value\u003e)\n     4 File \"unknown\"\n         In AUTHENTICATE\n       Args (#\u003csecret value\u003e)\n  \n  Condition: Network timeout\n```\nNow both third and fourth frames show `#\u003csecret value\u003e` instead of the password.\nThis is because `(log4cl-extras/secrets:make-secrets-replacer)` call returns a closure\nwhich remembers and replaces raw values of the secrets too!\n\n\u003ca id=\"but-this-is-not-a-silver-bullet\"\u003e\u003c/a\u003e\n\n#### But this is not a silver bullet\n\nApproach described above works only if the secret value is passed from one function to another.\nSometimes you might create a secret value and pass it to the code which does not support secret-values.\n\nThe simplest example is passing Authorization header with a token. Usually you code will look like\nthis:\n\n```lisp\n(let ((headers (list (cons \"Authorization\"\n                           (concatenate 'string\n                                        \"bearer \"\n                                        (secret-values:ensure-value-revealed *token*))))))\n   (dex:get url :headers headers))\n```\nIn this code `*token*` is secret, but `headers` now contains another secret where \"bearer \" is concatenated with prefix.\nAnd we can not wrap this header into a secret-value because `dexador` does not support them. Because of this, if some\nerror will be signalled from `dex:get`, then this secret header will be printed by [`log4cl-extras/error:with-log-unhandled`][3fd6].\n\nTo solve the problem, we need to tell log4cl-extras that there is a secret value used somewhere on the stack. To do this,\nwrap unsafe code with [`log4cl-extras/secrets:with-secrets`][fa0e]:\n\n```lisp\n(let ((value (concatenate 'string\n                          \"bearer \"\n                          (secret-values:ensure-value-revealed *token*)))\n      (headers (list (cons \"Authorization\"\n                           header))))\n   (with-secrets (value)\n     (dex:get url :headers headers)))\n```\nThis way, [`log4cl-extras/error:with-log-unhandled`][3fd6] macro will be able to mask this `value` with a placeholder.\n\n\u003ca id=\"x-28LOG4CL-EXTRAS-2FSECRETS-3A-3A-40HARD-WAY-2040ANTS-DOC-2FLOCATIVES-3ASECTION-29\"\u003e\u003c/a\u003e\n\n### Hard Way\n\nSometimes it is desireable to remove from tracebacks other kinds of data.\nFor example I don't want to see [Lack][d1aa]'s\nenvironments, because of a few reasons:\n\n* they contain cookies and it is insecure to log them;\n* they may contain `HTTP` header with tokens;\n* env objects are list with large amount of data and this makes tracebacks unreadable.\n\nLet's create a filter for arguments, which will replace Lack's environments\nwith a placeholder.\n\nFirst, we need to create a placeholder object:\n\n```\nCL-USER\u003e (defvar +lack-env-placeholder+\n           (log4cl-extras/error:make-placeholder \"lack env\"))\n+LACK-ENV-PLACEHOLDER+\n```\nNext, we need to define a filter function. Each filter function should accept\ntwo arguments:\n\n* a function's name, which can be a symbol or a list like `(:method foo-bar (...))`\n* a list of arguments.\n\nFilter should return two values, which can be the same is inputs or a transformed in some way.\n\nFor example, we know that the Lack's env is a plist with `:REQUEST-METHOD`, `:REQUEST-URI` and other values.\nWe can to write a predicate like this:\n\n```\nCL-USER\u003e (defun lack-env-p (arg)\n           (and (listp arg)\n                (member :request-method arg)\n                (member :request-uri arg)))\n```\nAnd to use it in our filter:\n\n```\nCL-USER\u003e (defun remove-lack-env-from-frame (func-name args)\n           \"Removes Lack's env from stackframes to make backtrace concise.\"\n           (values func-name\n                   (loop for arg in args\n                         if (lack-env-p arg)\n                           collect +lack-env-placeholder+\n                         else\n                           collect arg)))\n```\nNow let's try to use it:\n\n```\nCL-USER\u003e (defun request-handler (app env)\n           (authenticate (secret-values:conceal-value\n                          \"Secret password\"))\n           (pass-further app env))\n   \nCL-USER\u003e (setf log4cl-extras/error:*args-filters*\n               (list 'remove-lack-env-from-frame)\n               log4cl-extras/error:*args-filter-constructors*\n               ;; We need this too to keep DB password safe, remember?\n               (list 'log4cl-extras/secrets:make-secrets-replacer))\n```\nNow pay attention to the fifth frame, where second argument is replaced\nwith `#\u003clack env\u003e`!!!\n\n```\nCL-USER\u003e (log4cl-extras/error:with-log-unhandled (:depth 7)\n           (request-handler 42\n                            (list :request-method :post\n                                  :request-uri \"/login/\"\n                                  :cookies \"Session hash, and other secrets.\")))\n\u003cERROR\u003e [2021-01-24T14:56:45.502656+03:00] Unhandled exception\n  Fields:\n  Traceback (most recent call last):\n     0 File \"unknown\"\n         In (FLET \"H0\")\n       Args (#\u003cSIMPLE-ERROR \"Network timeout\" {1004233EB3}\u003e)\n     1 File \"/Users/art/.roswell/src/sbcl-2.0.11/src/code/cold-error.lisp\", line 81\n         In SB-KERNEL::%SIGNAL\n       Args (#\u003cSIMPLE-ERROR \"Network timeout\" {1004233EB3}\u003e)\n     2 File \"/Users/art/.roswell/src/sbcl-2.0.11/src/code/cold-error.lisp\", line 154\n         In ERROR\n       Args (\"Network timeout\")\n     3 File \"unknown\"\n         In CONNECT\n       Args (#\u003csecret value\u003e)\n     4 File \"unknown\"\n         In AUTHENTICATE\n       Args (#\u003csecret value\u003e)\n     5 File \"unknown\"\n         In REQUEST-HANDLER\n       Args (42 #\u003clack env\u003e)\n     6 File \"unknown\"\n         In (LAMBDA ())\n       Args ()\n     \n  Condition: Network timeout\n```\nFor such simple case like replacing args matching a predicate, [`log4cl-extras`][1395] has a small helper [`log4cl-extras/error:make-args-filter`][9b0c]:\n\n```\nCL-USER\u003e (setf log4cl-extras/error:*args-filters*\n               (list (log4cl-extras/error:make-args-filter\n                      'lack-env-p\n                      (log4cl-extras/error:make-placeholder \"LACK ENV BEING HERE\")))\n               log4cl-extras/error:*args-filter-constructors*\n               ;; We need this too to keep DB password safe, remember?\n               (list 'log4cl-extras/secrets:make-secrets-replacer))\n   \n\u003cERROR\u003e [2021-01-24T15:09:48.839513+03:00] Unhandled exception\n  Fields:\n  Traceback (most recent call last):\n     0 File \"unknown\"\n         In (FLET \"H0\")\n       Args (#\u003cSIMPLE-ERROR \"Network timeout\" {1003112243}\u003e)\n     1 File \"/Users/art/.roswell/src/sbcl-2.0.11/src/code/cold-error.lisp\", line 81\n         In SB-KERNEL::%SIGNAL\n       Args (#\u003cSIMPLE-ERROR \"Network timeout\" {1003112243}\u003e)\n     2 File \"/Users/art/.roswell/src/sbcl-2.0.11/src/code/cold-error.lisp\", line 154\n         In ERROR\n       Args (\"Network timeout\")\n     3 File \"unknown\"\n         In CONNECT\n       Args (#\u003csecret value\u003e)\n     4 File \"unknown\"\n         In AUTHENTICATE\n       Args (#\u003csecret value\u003e)\n     5 File \"unknown\"\n         In REQUEST-HANDLER\n       Args (42 #\u003cLACK ENV BEING HERE\u003e)\n     6 File \"unknown\"\n         In (LAMBDA ())\n       Args ()\n  \n  Condition: Network timeout\n```\n\u003ca id=\"x-28LOG4CL-EXTRAS-2FSECRETS-3AMAKE-SECRETS-REPLACER-20FUNCTION-29\"\u003e\u003c/a\u003e\n\n### [function](e106) `log4cl-extras/secrets:make-secrets-replacer` \u0026key secrets\n\nReturns a function which can be used to filter backtrace arguments.\n\nBeware, don't add result of the call to [`make-secrets-replacer`][bb11] to\nthe [`log4cl-extras/error:*args-filters*`][c7a0] variable, because it will\ncollect all secrets and keep them in memory until the end of the program.\n\nUse [`log4cl-extras/error:*args-filter-constructors*`][5c08] instead, to keep\nsecrets only during the backtrace processing.\n\nOptional list of secret values can be passed as a `SECRETS` argument.\nValues of this list should be either strings or `SECRET-VALUES:SECRET-VALUE` instances.\n\n\u003ca id=\"x-28LOG4CL-EXTRAS-2FSECRETS-3AWITH-SECRETS-20-2840ANTS-DOC-2FLOCATIVES-3AMACRO-29-29\"\u003e\u003c/a\u003e\n\n### [macro](5cdf) `log4cl-extras/secrets:with-secrets` (\u0026rest secret-values) \u0026body body\n\nIf any exception will happen during the body execution and be logged by\n[`log4cl-extras/error:with-log-unhandled`][3fd6] macro, listed `SECRET-VALUES` will be replaced by placeholders.\n\nThis is useful when you don't have a `SECRET-VALUES:SECRET-VALUE` passed somewhere on the stack. In this\ncase this macro provides a way how to let backtrace generator knowledge about values which should not be logged.\n\n\u003ca id=\"x-28LOG4CL-EXTRAS-2FAPPENDERS-3A-3A-40APPENDERS-2040ANTS-DOC-2FLOCATIVES-3ASECTION-29\"\u003e\u003c/a\u003e\n\n## Appenders\n\nIn case of errors, `LOG4CL` removes appender from the logger. After that log message will be lost.\n\nI don't like this behaviour and prefer to see such errors in logs and to log other\nmessages. This library defines a special appender classes which are not removed on errors but\noutput this message instead: \"Caught `SOME-ERROR`: Error description - Unable to log the message.\".\n\nWhen you use [`log4cl-extras/config:setup`][74de] function it automatically uses these appenders.\n\nTo debug logging errors interactively, you can set [`*debug-on-error*`][8400] variable to T.\n\n\u003ca id=\"x-28LOG4CL-EXTRAS-2FAPPENDERS-3ASTABLE-DAILY-FILE-APPENDER-20CLASS-29\"\u003e\u003c/a\u003e\n\n### [class](a2c9) `log4cl-extras/appenders:stable-daily-file-appender` (dont-disable-mixin daily-file-appender)\n\n\u003ca id=\"x-28LOG4CL-EXTRAS-2FAPPENDERS-3ASTABLE-FILE-APPENDER-20CLASS-29\"\u003e\u003c/a\u003e\n\n### [class](5a9b) `log4cl-extras/appenders:stable-file-appender` (dont-disable-mixin file-appender)\n\n\u003ca id=\"x-28LOG4CL-EXTRAS-2FAPPENDERS-3ASTABLE-THIS-CONSOLE-APPENDER-20CLASS-29\"\u003e\u003c/a\u003e\n\n### [class](10db) `log4cl-extras/appenders:stable-this-console-appender` (dont-disable-mixin this-console-appender)\n\n\u003ca id=\"x-28LOG4CL-EXTRAS-2FAPPENDERS-3ADONT-DISABLE-MIXIN-20CLASS-29\"\u003e\u003c/a\u003e\n\n### [class](a7b4) `log4cl-extras/appenders:dont-disable-mixin` ()\n\n\u003ca id=\"x-28LOG4CL-EXTRAS-2FAPPENDERS-3A-2ADEBUG-ON-ERROR-2A-20-28VARIABLE-29-29\"\u003e\u003c/a\u003e\n\n### [variable](1e6f) `log4cl-extras/appenders:*debug-on-error*` nil\n\nWhen T, then `INVOKE-DEBUGGER` will be called in case of any error during logging the message.\n\n\n[ee75]: https://40ants.com/lisp-project-of-the-day/2020/09/0186-secret-values.html\n[2906]: https://40ants.com/log4cl-extras/\n[1395]: https://40ants.com/log4cl-extras/#x-28-23A-28-2813-29-20BASE-CHAR-20-2E-20-22log4cl-extras-22-29-20ASDF-2FSYSTEM-3ASYSTEM-29\n[8400]: https://40ants.com/log4cl-extras/#x-28LOG4CL-EXTRAS-2FAPPENDERS-3A-2ADEBUG-ON-ERROR-2A-20-28VARIABLE-29-29\n[74de]: https://40ants.com/log4cl-extras/#x-28LOG4CL-EXTRAS-2FCONFIG-3ASETUP-20FUNCTION-29\n[b464]: https://40ants.com/log4cl-extras/#x-28LOG4CL-EXTRAS-2FCONTEXT-3AWITH-FIELDS-20-2840ANTS-DOC-2FLOCATIVES-3AMACRO-29-29\n[5c08]: https://40ants.com/log4cl-extras/#x-28LOG4CL-EXTRAS-2FERROR-3A-2AARGS-FILTER-CONSTRUCTORS-2A-20-28VARIABLE-29-29\n[c7a0]: https://40ants.com/log4cl-extras/#x-28LOG4CL-EXTRAS-2FERROR-3A-2AARGS-FILTERS-2A-20-28VARIABLE-29-29\n[9b0c]: https://40ants.com/log4cl-extras/#x-28LOG4CL-EXTRAS-2FERROR-3AMAKE-ARGS-FILTER-20FUNCTION-29\n[de65]: https://40ants.com/log4cl-extras/#x-28LOG4CL-EXTRAS-2FERROR-3AMAKE-PLACEHOLDER-20FUNCTION-29\n[6a57]: https://40ants.com/log4cl-extras/#x-28LOG4CL-EXTRAS-2FERROR-3APRINT-BACKTRACE-20FUNCTION-29\n[3fd6]: https://40ants.com/log4cl-extras/#x-28LOG4CL-EXTRAS-2FERROR-3AWITH-LOG-UNHANDLED-20-2840ANTS-DOC-2FLOCATIVES-3AMACRO-29-29\n[f356]: https://40ants.com/log4cl-extras/#x-28LOG4CL-EXTRAS-2FJSON-3AJSON-LAYOUT-20CLASS-29\n[f2d0]: https://40ants.com/log4cl-extras/#x-28LOG4CL-EXTRAS-2FPLAIN-3APLAIN-LAYOUT-20CLASS-29\n[2087]: https://40ants.com/log4cl-extras/#x-28LOG4CL-EXTRAS-2FSECRETS-3A-3A-40HARD-WAY-2040ANTS-DOC-2FLOCATIVES-3ASECTION-29\n[bb11]: https://40ants.com/log4cl-extras/#x-28LOG4CL-EXTRAS-2FSECRETS-3AMAKE-SECRETS-REPLACER-20FUNCTION-29\n[fa0e]: https://40ants.com/log4cl-extras/#x-28LOG4CL-EXTRAS-2FSECRETS-3AWITH-SECRETS-20-2840ANTS-DOC-2FLOCATIVES-3AMACRO-29-29\n[8f00]: https://github.com/40ants/log4cl-extras\n[b509]: https://github.com/40ants/log4cl-extras/actions\n[1e6f]: https://github.com/40ants/log4cl-extras/blob/b6e7501de67a91f7d738306acc131b7dbb871111/src/appenders.lisp#L17\n[a7b4]: https://github.com/40ants/log4cl-extras/blob/b6e7501de67a91f7d738306acc131b7dbb871111/src/appenders.lisp#L21\n[10db]: https://github.com/40ants/log4cl-extras/blob/b6e7501de67a91f7d738306acc131b7dbb871111/src/appenders.lisp#L25\n[a2c9]: https://github.com/40ants/log4cl-extras/blob/b6e7501de67a91f7d738306acc131b7dbb871111/src/appenders.lisp#L29\n[5a9b]: https://github.com/40ants/log4cl-extras/blob/b6e7501de67a91f7d738306acc131b7dbb871111/src/appenders.lisp#L33\n[afb7]: https://github.com/40ants/log4cl-extras/blob/b6e7501de67a91f7d738306acc131b7dbb871111/src/config.lisp#L211\n[6b38]: https://github.com/40ants/log4cl-extras/blob/b6e7501de67a91f7d738306acc131b7dbb871111/src/context.lisp#L62\n[b23c]: https://github.com/40ants/log4cl-extras/blob/b6e7501de67a91f7d738306acc131b7dbb871111/src/context.lisp#L78\n[d588]: https://github.com/40ants/log4cl-extras/blob/b6e7501de67a91f7d738306acc131b7dbb871111/src/error.lisp#L152\n[616b]: https://github.com/40ants/log4cl-extras/blob/b6e7501de67a91f7d738306acc131b7dbb871111/src/error.lisp#L155\n[56cd]: https://github.com/40ants/log4cl-extras/blob/b6e7501de67a91f7d738306acc131b7dbb871111/src/error.lisp#L158\n[7d10]: https://github.com/40ants/log4cl-extras/blob/b6e7501de67a91f7d738306acc131b7dbb871111/src/error.lisp#L172\n[5328]: https://github.com/40ants/log4cl-extras/blob/b6e7501de67a91f7d738306acc131b7dbb871111/src/error.lisp#L296\n[176d]: https://github.com/40ants/log4cl-extras/blob/b6e7501de67a91f7d738306acc131b7dbb871111/src/error.lisp#L376\n[00a5]: https://github.com/40ants/log4cl-extras/blob/b6e7501de67a91f7d738306acc131b7dbb871111/src/error.lisp#L394\n[4e5b]: https://github.com/40ants/log4cl-extras/blob/b6e7501de67a91f7d738306acc131b7dbb871111/src/error.lisp#L395\n[d802]: https://github.com/40ants/log4cl-extras/blob/b6e7501de67a91f7d738306acc131b7dbb871111/src/error.lisp#L413\n[c537]: https://github.com/40ants/log4cl-extras/blob/b6e7501de67a91f7d738306acc131b7dbb871111/src/error.lisp#L428\n[ce37]: https://github.com/40ants/log4cl-extras/blob/b6e7501de67a91f7d738306acc131b7dbb871111/src/error.lisp#L432\n[1a91]: https://github.com/40ants/log4cl-extras/blob/b6e7501de67a91f7d738306acc131b7dbb871111/src/json.lisp#L54\n[1ba4]: https://github.com/40ants/log4cl-extras/blob/b6e7501de67a91f7d738306acc131b7dbb871111/src/plain.lisp#L77\n[e106]: https://github.com/40ants/log4cl-extras/blob/b6e7501de67a91f7d738306acc131b7dbb871111/src/secrets.lisp#L399\n[5cdf]: https://github.com/40ants/log4cl-extras/blob/b6e7501de67a91f7d738306acc131b7dbb871111/src/secrets.lisp#L458\n[d7d4]: https://github.com/40ants/log4cl-extras/issues\n[d1aa]: https://github.com/fukamachi/lack/\n[2c00]: https://quickdocs.org/40ants-doc\n[8236]: https://quickdocs.org/alexandria\n[2ecb]: https://quickdocs.org/cl-strings\n[70a8]: https://quickdocs.org/dissect\n[07be]: https://quickdocs.org/global-vars\n[6dd8]: https://quickdocs.org/jonathan\n[46a1]: https://quickdocs.org/local-time\n[7f8b]: https://quickdocs.org/log4cl\n[d0a9]: https://quickdocs.org/named-readtables\n[c01d]: https://quickdocs.org/pythonic-string-reader\n[9201]: https://quickdocs.org/with-output-to-stream\n[646b]: https://www.datadoghq.com/\n[ee55]: https://www.papertrail.com/\n\n* * *\n###### [generated by [40ANTS-DOC](https://40ants.com/doc/)]\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F40ants%2Flog4cl-extras","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F40ants%2Flog4cl-extras","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F40ants%2Flog4cl-extras/lists"}