{"id":19155920,"url":"https://github.com/lyokha/nginx-log-plugin","last_synced_at":"2026-02-22T12:21:53.426Z","repository":{"id":56861616,"uuid":"305748546","full_name":"lyokha/nginx-log-plugin","owner":"lyokha","description":"Native Nginx logging from configuration files and Haskell handlers","archived":false,"fork":false,"pushed_at":"2024-08-23T10:28:43.000Z","size":86,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-15T11:40:03.727Z","etag":null,"topics":["binding","haskell","logging","nginx","web"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lyokha.png","metadata":{"files":{"readme":"README.md","changelog":"Changelog.md","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}},"created_at":"2020-10-20T15:14:59.000Z","updated_at":"2024-08-23T10:28:46.000Z","dependencies_parsed_at":"2023-11-30T13:30:08.550Z","dependency_job_id":"73238c78-bfc0-4de4-9d69-8ad60b8bc8b6","html_url":"https://github.com/lyokha/nginx-log-plugin","commit_stats":{"total_commits":84,"total_committers":1,"mean_commits":84.0,"dds":0.0,"last_synced_commit":"6a96a8d9f317ae3ae255d0e0df34e2404b0bcfdb"},"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lyokha%2Fnginx-log-plugin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lyokha%2Fnginx-log-plugin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lyokha%2Fnginx-log-plugin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lyokha%2Fnginx-log-plugin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lyokha","download_url":"https://codeload.github.com/lyokha/nginx-log-plugin/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252833936,"owners_count":21811277,"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","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":["binding","haskell","logging","nginx","web"],"created_at":"2024-11-09T08:32:42.380Z","updated_at":"2026-02-22T12:21:53.412Z","avatar_url":"https://github.com/lyokha.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"Native Nginx logging from configuration files and Haskell handlers\n==================================================================\n\n[![Build Status](https://github.com/lyokha/nginx-log-plugin/workflows/CI/badge.svg)](https://github.com/lyokha/nginx-log-plugin/actions?query=workflow%3ACI)\n[![Hackage](https://img.shields.io/hackage/v/ngx-export-log.svg?label=hackage%20%7C%20ngx-export-log\u0026logo=haskell\u0026logoColor=%239580D1)](https://hackage.haskell.org/package/ngx-export-log)\n\n**Disclaimer**: this is not an Nginx module in the traditional sense! It\ncompiles to a shared library that gets loaded in Nginx using directive\n`haskell load` from Nginx module\n[*nginx-haskell-module*](https://github.com/lyokha/nginx-haskell-module). Let's\ncall this *plugin*. The plugin provides support for logging messages from\nconfiguration files in the run-time using the native Nginx logging mechanism\navailable with directive `error_log`.\n\nTable of contents\n-----------------\n\n- [Directives and custom handlers](#directives-and-custom-handlers)\n- [An example](#an-example)\n- [High-level directives log and log ultimate](#high-level-directives-log-and-log-ultimate)\n- [Building and installation](#building-and-installation)\n\nDirectives and custom handlers\n------------------------------\n\nThere are two flavours of logging directives. Directives `logStderr`,\n`logEmerg`, `logAlert`, `logCrit`, `logErr`, `logWarn`, `logNotice`, `logInfo`,\nand `logDebug` write to the *global* error log associated with the main\nconfiguration level (i.e. the level outside of the *http* clause), while their\n*R*-counterparts `logStderrR`, `logEmergR`, `logAlertR`, `logCritR`, `logErrR`,\n`logWarnR`, `logNoticeR`, `logInfoR`, and `logDebugR` write to a specific for\nthe current location *http* error log. The *R* directives require the request\ncontext, and therefore they are heavier in use and speed than the *simple* (or\n*global*) logging directives, however they log the useful request context data\nwhich is unavailable in the global context.\n\nHaskell functions of the same names as the logging directives can be used in\ncustom Haskell handlers. Besides them, there are three generic functions `logG`,\n`logR`, and `logM` which expect a log level of type `LogLevel` as their first\nargument (this type contains values `LogStderr`, `LogEmerg`, `LogAlert`,\n`LogCrit`, `LogErr`, `LogWarn`, `LogNotice`, `LogInfo`, and `LogDebug`).\nFunction `logM` is essentially `logR` with the request context explicitly\nseparated from the log message in the parameters. The *global* logging functions\ncan be used in custom *services* and *service hooks* (in terms of\n*nginx-haskell-module*), while the *R* functions (including `logM`) are useful\nin other synchronous and asynchronous handlers.\n\nAn example\n----------\n\n###### File *ngx_log.hs*\n\n```haskell\n{-# LANGUAGE TemplateHaskell, TupleSections, MagicHash #-}\n\nmodule NgxLog where\n\nimport           NgxExport\nimport           NgxExport.Tools.Read (skipRPtr)\n\nimport           NgxExport.Log (logR, LogLevel (..))\n\nimport           Data.ByteString (ByteString)\nimport qualified Data.ByteString.Char8 as C8\nimport qualified Data.ByteString.Lazy as L\nimport qualified Data.ByteString.Lazy.Char8 as C8L\nimport           Data.ByteString.Unsafe (unsafePackAddressLen)\nimport           Data.ByteString.Internal (accursedUnutterablePerformIO)\nimport           Data.Char\nimport           GHC.Prim\n\npackLiteral :: Int -\u003e GHC.Prim.Addr# -\u003e ByteString\npackLiteral l s = accursedUnutterablePerformIO $ unsafePackAddressLen l s\n\ntee :: ByteString -\u003e IO ContentHandlerResult\ntee msg = do\n    logR LogInfo msg\n    return $ (, packLiteral 10 \"text/plain\"#, 200, []) $\n        flip C8L.snoc '\\n' $ L.fromStrict $ C8.dropWhile isSpace $ skipRPtr msg\n\nngxExportAsyncHandler 'tee\n```\n\nHere we used function `logR` with argument `LogInfo` to make asynchronous\ncontent handler *tee* that echoes its argument both in the response body and the\nerror log. All Haskell handlers used in logging directives are exported\nautomatically.\n\n###### File *nginx.conf*\n\n```nginx\nuser                    nobody;\nworker_processes        4;\n\nevents {\n    worker_connections  1024;\n}\n\nerror_log               /tmp/nginx-test-error-g.log info;\n\nhttp {\n    default_type        application/octet-stream;\n    sendfile            on;\n\n    error_log           /tmp/nginx-test-error.log info;\n    access_log          /tmp/nginx-test-access.log;\n\n    haskell load        /var/lib/nginx/ngx_log.so;\n\n    server {\n        listen          8010;\n        server_name     main;\n\n        haskell_run logInfo !$msgG \"Write in global log!\";\n\n        location / {\n            haskell_run logInfoR \u003c~$msg '$_r_ptr\n                    Got query \"$args\"';\n\n            if ($arg_a) {\n                haskell_run logInfoR \u003c~$msg '$_r_ptr\n                        Got a = \"$arg_a\"';\n            }\n\n            haskell_run logInfoR !$msgR '$_r_ptr\n                    Request finished';\n\n            echo Ok;\n        }\n\n        location /tee {\n            haskell_async_content tee \"$_r_ptr\n                    Hello, world!\";\n        }\n    }\n}\n```\n\nThere is the *global* error log */tmp/nginx-test-error-g.log* where directive\n`logInfo` will write to, and an *http* error log */tmp/nginx-test-error.log*\ndeclared inside the *http* clause where directives `logInfoR` will write to.\nNotice that the *R* directives and handlers require variable `$_r_ptr` to\nproperly log messages: missing this variable may cause crashes of Nginx worker\nprocesses! Notice also that we used a *strict volatile* variable `\u003c~$msg` for\nlogging *early* messages inside locations: using a single regular strict early\nvariable (say `\u003c!$msg`) in a single request which would have triggered *if*\nblocks and rewrites between locations could lead to missing log messages.\n\nNote that in *nginx-haskell-module* version *3.0* and higher, the *handler(r)*\nsyntax is available which means that directives using handler's arguments\nwith `$_r_ptr` in the beginning, for example\n\n```nginx\n            haskell_async_content tee \"$_r_ptr\n                    Hello, world!\";\n```\n\ncan be rewritten in a more nice-looking style like\n\n```nginx\n            haskell_async_content tee(r) \"Hello, world!\";\n```\n\n###### A simple test\n\nLet's watch the log files,\n\n```ShellSession\n$ tail -f /tmp/nginx-test-*\n```\n\nand run a test in another terminal.\n\n```ShellSession\n$ curl 'http://localhost:8010/?a=hello\u0026b=world'\nOk\n$ curl 'http://localhost:8010/tee'\nHello, world!\n```\n\nIn the first terminal the following lines should appear.\n\n```ShellSession\n==\u003e /tmp/nginx-test-error.log \u003c==\n2020/10/21 12:33:28 [info] 20676#0: *1 Got query \"a=hello\u0026b=world\", client: 127.0.0.1, server: main, request: \"GET /?a=hello\u0026b=world HTTP/1.1\", host: \"localhost:8010\"\n2020/10/21 12:33:28 [info] 20676#0: *1 Got a = \"hello\", client: 127.0.0.1, server: main, request: \"GET /?a=hello\u0026b=world HTTP/1.1\", host: \"localhost:8010\"\n\n==\u003e /tmp/nginx-test-access.log \u003c==\n127.0.0.1 - - [21/Oct/2020:12:33:28 +0300] \"GET /?a=hello\u0026b=world HTTP/1.1\" 200 13 \"-\" \"curl/7.69.1\"\n\n==\u003e /tmp/nginx-test-error-g.log \u003c==\n2020/10/21 12:33:28 [info] 20676#0: Write in global log!\n\n==\u003e /tmp/nginx-test-error.log \u003c==\n2020/10/21 12:33:28 [info] 20676#0: *1 Request finished, client: 127.0.0.1, server: main, request: \"GET /?a=hello\u0026b=world HTTP/1.1\", host: \"localhost:8010\"\n2020/10/21 12:33:28 [info] 20676#0: *1 client 127.0.0.1 closed keepalive connection\n2020/10/21 12:33:30 [info] 20676#0: *2 Hello, world!, client: 127.0.0.1, server: main, request: \"GET /tee HTTP/1.1\", host: \"localhost:8010\"\n\n==\u003e /tmp/nginx-test-access.log \u003c==\n127.0.0.1 - - [21/Oct/2020:12:33:30 +0300] \"GET /tee HTTP/1.1\" 200 14 \"-\" \"curl/7.69.1\"\n\n==\u003e /tmp/nginx-test-error-g.log \u003c==\n2020/10/21 12:33:30 [info] 20676#0: Write in global log!\n\n==\u003e /tmp/nginx-test-error.log \u003c==\n2020/10/21 12:33:30 [info] 20676#0: *2 client 127.0.0.1 closed keepalive connection\n```\n\nSee more [*examples of typical use cases and\ngotchas*](https://github.com/lyokha/nginx-log-plugin/blob/1b3cc6f6f037f73671c1fea19299e45babeadb39/test/nginx.conf#L81).\n\nHigh-level directives log and log ultimate\n------------------------------------------\n\nDirectives `haskell_run log...` are overflowed with gory details including\nembarrassing handler variables and variable `$_r_ptr`. Directives `log` and\n`log ultimate` were introduced to hide these details by setting reasonable\ndefaults. For using them, an additional Nginx *dynamic module* must be built and\nloaded.\n\n```nginx\nload_module             /var/lib/nginx/modules/ngx_log_plugin_module.so;\n```\n\nLocation */* from the example shown in the previous section could be rewritten\nas\n\n```nginx\n        location / {\n            log info 'Got query \"$args\"';\n\n            if ($arg_a) {\n                log info 'Got a = \"$arg_a\"';\n            }\n\n            log ultimate info 'Request finished';\n\n            if ($arg_c) {\n                rewrite ^ /rewr last;\n            }\n\n            echo Ok;\n        }\n```\n\nDirective `log` introduces handler variables `\u003c!$_log_Rsrv0` through\n`\u003c!$_log_Rsrv3` in *server* clauses and `\u003c~$_log_V0` through `\u003c~$_log_V3` in\n*location* and *location-if* clauses. Directive `log ultimate` introduces\nhandler variables `!$_log_Usrv0` through `!$_log_Usrv3` in *server* clauses and\n`!$_log_U0` through `!$_log_U3` in *location* and *location-if* clauses. The\nextra variables with suffixes *1* through *3* are used when the directives get\ndeclared multiple times at a single configuration level.\n\nBoth directives `log` and `log ultimate` use the *R* log handlers which means\nthat there is no high-level log directive for writing into the global log.\n\nBuilding and installation\n-------------------------\n\n**Note**: due to a [bug](https://gitlab.haskell.org/ghc/ghc/-/issues/26136)\nintroduced in GHC major versions *9.12*, *9.10*, and *9.8*, the code won't\ncompile with GHC *9.12.2*, *9.12.1* (fixed in *9.12.4*), *9.10.2* (fixed in\n*9.10.3*), *9.8.4* (not fixed, compile with *9.8.2*).\n\nThe plugin contains Haskell and C parts, and thus, it requires *ghc*, *cabal*,\n*gcc*, and a directory with the Nginx sources. The build tool also requires\n[*patchelf*](https://github.com/NixOS/patchelf) and utility *nhm-tool* which is\nshipped with package\n[*ngx-export-distribution*](https://hackage.haskell.org/package/ngx-export-distribution).\n\nLet's first install the Nginx module. For this, go to the directory with the\nNginx source code,\n\n```ShellSession\n$ cd /path/to/nginx/sources\n```\n\ncompile,\n\n```ShellSession\n$ ./configure --add-dynamic-module=/path/to/nginx-log-plugin/sources --add-dynamic-module=/path/to/nginx-log-plugin/sources/module\n$ make modules\n```\n\nand install *ngx_log_plugin.so*.\n\n```ShellSession\n$ export NGX_HS_INSTALL_DIR=/var/lib/nginx\n$ sudo install -d $NGX_HS_INSTALL_DIR\n$ sudo cp objs/ngx_log_plugin.so $NGX_HS_INSTALL_DIR/libngx_log_plugin.so\n```\n\nNotice that we added prefix *lib* to the module's name!\n\nFor using directives `log` and `log ultimate`, the dynamic module\n*ngx_log_plugin_module* must also be installed.\n\n```ShellSession\n$ sudo install -d $NGX_HS_INSTALL_DIR/modules\n$ sudo cp objs/ngx_log_plugin_module.so $NGX_HS_INSTALL_DIR/modules\n```\n\nNow let's build the Haskell code.\n\n```ShellSession\n$ cd -\n$ cd simple\n```\n\nThen\n\n```ShellSession\n$ make PREFIX=$NGX_HS_INSTALL_DIR\n$ sudo make PREFIX=$NGX_HS_INSTALL_DIR install\n```\n\nor simply\n\n```ShellSession\n$ make\n$ sudo make install\n```\n\nif installation directory is */var/lib/nginx/*.\n\nWith ghc older than *9.0.1*, build with\n\n```ShellSession\n$ make LINKRTS=-lHSrts_thr-ghc$(ghc --numeric-version)\n```\n\nBy default, package *ngx-export-log* gets installed from *Hackage*. To build it\nlocally, augment stanza *packages* inside\n[*cabal.project*](simple/cabal.project) according to the commentary attached to\nit.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flyokha%2Fnginx-log-plugin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flyokha%2Fnginx-log-plugin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flyokha%2Fnginx-log-plugin/lists"}