{"id":20434177,"url":"https://github.com/gilcrest/httplog","last_synced_at":"2025-04-12T21:11:45.826Z","repository":{"id":57483480,"uuid":"146137410","full_name":"gilcrest/httplog","owner":"gilcrest","description":"HTTP Logging Middleware for Go","archived":false,"fork":false,"pushed_at":"2021-08-31T01:41:05.000Z","size":285,"stargazers_count":13,"open_issues_count":4,"forks_count":4,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-10T17:13:35.687Z","etag":null,"topics":["golang","http-logger","middleware"],"latest_commit_sha":null,"homepage":null,"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/gilcrest.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":"audit.go","citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-08-25T23:44:54.000Z","updated_at":"2025-03-15T23:20:44.000Z","dependencies_parsed_at":"2022-08-27T21:02:21.454Z","dependency_job_id":null,"html_url":"https://github.com/gilcrest/httplog","commit_stats":null,"previous_names":[],"tags_count":23,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gilcrest%2Fhttplog","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gilcrest%2Fhttplog/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gilcrest%2Fhttplog/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gilcrest%2Fhttplog/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gilcrest","download_url":"https://codeload.github.com/gilcrest/httplog/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248631681,"owners_count":21136562,"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":["golang","http-logger","middleware"],"created_at":"2024-11-15T08:24:35.821Z","updated_at":"2025-04-12T21:11:45.800Z","avatar_url":"https://github.com/gilcrest.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# httplog - HTTP Request and Response Logging\n\n!!!!WARNING!!!! - This package works, but is something I wrote a long time ago and really needs to be updated. I logged Issue #8 to some day address this.\n\n## Installation\n\n```go\ngo get -u github.com/gilcrest/httplog\n```\n\n## GoDoc\n\n[https://pkg.go.dev/github.com/gilcrest/httplog](https://pkg.go.dev/github.com/gilcrest/httplog)\n\n## External Dependencies\n\n**httplog** has two external libraries that it depends on, listed below.\n\n- github.com/pkg/errors\n- github.com/rs/zerolog\n- github.com/rs/xid\n\nIf you plan to use the Database Logging feature of httplog, you will need to extract the httplogDDL.sql file included in the workspace and run this on your own PostgreSQL database. This script is pretty raw right now and will be made better if there is interest.\n\n## Overview\n\n**httplog** logs http requests and responses. It’s highly configurable, e.g. in production, log all response and requests, but don’t log the body or headers, in your dev environment log everything and so on. httplog also has different ways to log depending on your preference — structured logging via JSON, relational database logging or just plain standard library logging.\n\nhttplog has logic to turn on/off logging based on options you can either pass in to the middleware handler or from a JSON input file included with the library.\n\nhttplog offers three middleware choices, each of which adhere to fairly common middleware patterns: a simple HandlerFunc (`LogHandlerFunc`), a function (`LogHandler`) which takes a handler and returns a handler (often used with [alice](https://github.com/justinas/alice)) and finally, a function (`LogAdapter`), which returns an Adapter type (based on [Mat Ryer’s post](https://medium.com/@matryer/writing-middleware-in-golang-and-how-go-makes-it-so-much-fun-4375c1246e81)). An `httplog.Adapt` function and `httplog.Adapter` type are provided for the latter.\n\nBeyond logging request and response elements, **httplog** creates a unique id for each incoming request (using [xid](https://github.com/rs/xid)) and sets it (and a few other key request elements) into the request context. You can access these context items using provided helper functions, including a function that returns an `httplog.Audit` struct which bundles all these items for response payloads to provide clients with helpful information for support.\n\n## Features\n\n- [Middleware](#middleware)\n- [Configurable http request/response logging](#configurable-logging) (ability to turn on and off logging style based on file configuration)\n  - [Log Style 1](#log-style-1-structured-via-json): Structured (JSON), leveled (debug, error, info, etc.) logging to stdout\n  - [Log Style 2](#log-style-2-relational-db-logging-via-postgreSQL): Relational database (PostgreSQL) logging (certain data points broken out into standard column datatypes, request/response headers and body stored in TEXT datatype columns).\n  - [Log Style 3](#log-style-3-httputil-dumpRequest-or-dumpResponse): httputil DumpRequest or DumpResponse - there's not much to this, really - httplog just allows you to turn these standard library functions on or off through the configuration options\n- [Add Unique ID and Key Request Elements to Context](#add-unique-id-and-key-request-elements-to-context)\n- [Retrieve Unique ID and Key Request Elements from Context](#retrieve-unique-id-and-key-request-elements-from-context)\n- [Audit Struct for Response Payload](#audit-struct-for-response-payload)\n\n### Middleware\n\nEach middleware takes a minimum of three parameters:\n\n- `log` - an instance of zerolog.logger\n\n- `db` - a pointer to a sql database (PostgreSQL)\n  - You can set this parameter to nil if you are not planning to log to PostgreSQL\n\n- `o` - an `httplog.Opts` struct which has the all of the logging configurations\n  - You can set this parameter to nil and httplog will use options from the `httpLogOpt.json` file\n  - If you prefer not to use the `httpLogOpt.json` file, simply initialize the `httplog.Opts` struct and all values are set to false (the whole struct is boolean flags and in Go, a boolean's zero value is false). You can then pick and choose which flags to turn on via code.\n\n#### Middleware Examples\n\nThe below examples are taken from [go-API-template](https://github.com/gilcrest/go-API-template). It uses all three httplog middlewares for example sake. You obviously would choose 1 pattern and stick to that (for the most part).\n\n```go\npackage app\n\nimport (\n    \"github.com/gilcrest/go-API-template/datastore\"\n    \"github.com/gilcrest/httplog\"\n    \"github.com/justinas/alice\"\n)\n\n// routes registers handlers to the router\nfunc (s *server) routes() error {\n\n    // Get a logger instance from the server struct\n    log := s.logger\n\n    // Get pointer to logging database to pass into httplog\n    // Only need this if you plan to use the PostgreSQL\n    // logging style of httplog\n    logdb, err := s.ds.DB(datastore.LogDB)\n    if err != nil {\n        return err\n    }\n\n    // httplog.NewOpts gets a new httplog.Opts struct\n    // (with all flags set to false)\n    opts := httplog.NewOpts()\n\n    // For the examples below, I chose to turn on db logging only\n    // Log the request headers only (body has password on this api!)\n    // Log both the response headers and body\n    opts.Log2DB.Enable = true\n    opts.Log2DB.Request.Header = true\n    opts.Log2DB.Response.Header = true\n    opts.Log2DB.Response.Body = true\n\n    // HandlerFunc middleware example\n    // function takes an http.HandlerFunc and returns an http.HandlerFunc\n    // Also, match only POST requests with Content-Type header = application/json\n    s.router.HandleFunc(\"/v1/handlefunc/user\",\n        httplog.LogHandlerFunc(s.handleUserCreate(), log, logdb, opts)).\n        Methods(\"POST\").\n        Headers(\"Content-Type\", \"application/json\")\n\n    // function (`LogHandler`) that takes a handler and returns a handler (aka Constructor)\n    // (`func (http.Handler) http.Handler`)    - used with alice\n    // Also, match only POST requests with Content-Type header = application/json\n    s.router.Handle(\"/v1/alice/user\",\n        alice.New(httplog.LogHandler(log, logdb, opts)).\n            ThenFunc(s.handleUserCreate())).\n        Methods(\"POST\").\n        Headers(\"Content-Type\", \"application/json\")\n\n    // Adapter Type middleware example\n    // Also, match only POST requests with Content-Type header = application/json\n    s.router.Handle(\"/v1/adapter/user\",\n        httplog.Adapt(s.handleUserCreate(),\n            httplog.LogAdapter(log, logdb, opts))).\n        Methods(\"POST\").\n        Headers(\"Content-Type\", \"application/json\")\n\n    return nil\n}\n```\n\n----\n\n### Configurable Logging\n\nThe boolean fields found within the Opts struct type drive the rules for what logging features are turned on.  You can have one to three log styles turned on using this file (or none, if you so choose). Below are all the boolean options in the struct.\n\n\u003e Note: Right now, this struct for options serves its purpose and is pretty simple. I have read Dave Cheney's great post on [Functional Options for Friendly APIs](https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis) and it's great - I may switch to this style later.\n\n```go\n// Log2StdOut\nopts.Log2StdOut.Request.Enable\nopts.Log2StdOut.Request.Options.Header\nopts.Log2StdOut.Request.Options.Body\nopts.Log2StdOut.Response.Enable\nopts.Log2StdOut.Response.Options.Header\nopts.Log2StdOut.Response.Options.Body\n\n// Log2DB\nopts.Log2DB.Enable\nopts.Log2DB.Request.Header\nopts.Log2DB.Request.Body\nopts.Log2DB.Response.Header\nopts.Log2DB.Response.Body\n\n// DumpRequest\nopts.HTTPUtil.DumpRequest.Body\nopts.HTTPUtil.DumpRequest.Body\n```\n\n1. Pass an `Opts` struct when using one of the given middleware functions. `httplog.NewOpts` will return an Opts struct with all logging turned off. You can then set whichever logging style and option you like.\n1. If you do not pass an Opts struct to one of the provided middlewares, there is code in each that will import/marshal the `httpLogOpt.json` file found in the root of the httplog library into the `Opts` struct type. You can change log configuration by altering the boolean values present in this file.\n\n#### Log Style 1: Structured via JSON\n\n##### JSON Request Logging\n\nSet `log_json.Request.enable` in the [HTTP Log Config File](#log-config-file) or `opts.Log2StdOut.Request.Enable` to true in the `httplog.Opts` struct to enable http request logging as JSON (so long as you have properly \"chained\" the middleware).  The output for a request looks something like:\n\n```json\n{\"time\":1517970302,\"level\":\"info\",\"request_id\":\"b9t66vma6806ln8iak8g\",\"header_json\":\"{\\\"Accept\\\":[\\\"*/*\\\"],\\\"Accept-Encoding\\\":[\\\"gzip, deflate\\\"],\\\"Cache-Control\\\":[\\\"no-cache\\\"],\\\"Connection\\\":[\\\"keep-alive\\\"],\\\"Content-Length\\\":[\\\"129\\\"],\\\"Content-Type\\\":[\\\"application/json\\\"],\\\"Postman-Token\\\":[\\\"9949f5e5-b406-4e22-aff3-ab6ba6e7d841\\\"],\\\"User-Agent\\\":[\\\"PostmanRuntime/7.1.1\\\"]}\",\"body\":\"{\\\"username\\\": \\\"repoMan\\\",\\\"mobile_ID\\\": \\\"1-800-repoman\\\",\\\"email\\\":\\\"repoman@alwaysintense.com\\\",\\\"First_Name\\\":\\\"Otto\\\",\\\"Last_Name\\\":\\\"Maddox\\\"}\",\"method\":\"POST\",\"scheme\":\"http\",\"host\":\"127.0.0.1\",\"port\":\"8080\",\"path\":\"/api/v1/appuser\",\"protocol\":\"HTTP/1.1\",\"proto_major\":1,\"proto_minor\":1,\"Content Length\":129,\"Transfer-Encoding\":\"\",\"Close\":false,\"RemoteAddr\":\"127.0.0.1:58689\",\"RequestURI\":\"/api/v1/appuser\",\"message\":\"Request received\"}\n```\n\n\u003eNOTE - the HTTP header key:value pairs and json from the body are represented as escaped JSON within the actual message. If you don't want this data, set these fields to false in the JSON config file or `httplog.Opts` struct.\n\n##### JSON Response Logging\n\nSet `log_json.Response.enable` in the [HTTP Log Config File](#Log-Config-File) or `opts.Log2StdOut.Response.Enable` to true to enable http response logging as JSON. The response output will look something like:\n\n```json\n{\"time\":1517970302,\"level\":\"info\",\"request_id\":\"b9t66vma6806ln8iak8g\",\"response_code\":200,\"response_header\":\"{\\\"Content-Type\\\":[\\\"text/plain; charset=utf-8\\\"],\\\"Request-Id\\\":[\\\"b9t66vma6806ln8iak8g\\\"]}\",\"response_body\":\"{\\\"username\\\":\\\"repoMan\\\",\\\"mobile_id\\\":\\\"1-800-repoman\\\",\\\"email\\\":\\\"repoman@alwaysintense.com\\\",\\\"first_name\\\":\\\"Otto\\\",\\\"last_name\\\":\\\"Maddox\\\",\\\"create_user_id\\\":\\\"gilcrest\\\",\\\"create_date\\\":\\\"2018-02-06T21:25:02.538322Z\\\",\\\"update_user_id\\\":\\\"\\\",\\\"update_date\\\":\\\"0001-01-01T00:00:00Z\\\"}\\n{\\\"username\\\":\\\"repoMan\\\",\\\"mobile_id\\\":\\\"1-800-repoman\\\",\\\"email\\\":\\\"repoman@alwaysintense.com\\\",\\\"first_name\\\":\\\"Otto\\\",\\\"last_name\\\":\\\"Maddox\\\",\\\"create_user_id\\\":\\\"gilcrest\\\",\\\"create_date\\\":\\\"2018-02-06T21:25:02.538322Z\\\",\\\"update_user_id\\\":\\\"\\\",\\\"update_date\\\":\\\"0001-01-01T00:00:00Z\\\"}\\n\",\"message\":\"Response Sent\"}\n```\n\n\u003eNOTE - same as request - the HTTP header key:value pairs and json from the body are represented as escaped JSON within the actual message. If you don't want this data, set these fields to false in the JSON config file (`httpLogOpt.json`) or `httplog.Opts` struct.\n\n#### Log Style 2: Relational DB Logging via PostgreSQL\n\nSet `log_2DB.enable` to true in the [HTTP Log Config File](#Log-Config-File) to enable Database logging to a PostgreSQL database.  The DDL is provided within the ddl directory (`httplogDDL.sql`) and consists of one table and one stored function. Once enabled, Request and Response information will be logged as one transaction to the database.  You can optionally choose to log request and response headers using the Options fields within the [HTTP Log Config File](#Log-Config-File) or `httplog.Opts` struct.\n\n![Database Log](dbLog.png)\n\n##### Logging Database Table\n\nIn total 20 fields are logged as part of the database transaction.\n\n| Column Name   | Datatype    | Description          |\n| ------------- | ----------- | -------------------- |\n| request_id                | VARCHAR(100)  | Unique Request ID\n| client_id                 | VARCHAR(100)  | API Client ID\n| request_timestamp         | TIMESTAMP     | UTC time request received\n| response_code             | INTEGER       | HTTP Response Code\n| response_timestamp        | TIMESTAMP     | UTC time response sent\n| duration_in_millis        | BIGINT        | Response time duration in milliseconds\n| protocol                  | VARCHAR(20)   | HTTP protocol version, e.g. HTTP/1.1\n| protocol_major            | INTEGER       | HTTP protocol major version\n| protocol_minor            | INTEGER       | HTTP protocol minor version\n| request_method            | VARCHAR(10)   | HTTP method (GET, POST, PUT, etc.)\n| scheme                    | VARCHAR(100)  | URL scheme (http, https, etc.)\n| host                      | VARCHAR(100)  | URL host\n| port                      | VARCHAR(100)  | URL port\n| path                      | VARCHAR(4000) | URL path\n| remote_address            | VARCHAR(100)  | Network address which sent request\n| request_content_length    | BIGINT        | Request content length\n| request_header            | JSONB         | Key:Value pairs from HTTP request in JSON format\n| request_body              | TEXT          | Request body content\n| response_header           | JSONB         | Key:Value pairs from HTTP response in JSON format\n| response_body             | TEXT          | Response body content\n\n#### Log Style 3: httputil DumpRequest or DumpResponse\n\n##### httputil.DumpRequest\n\nSet `httputil.DumpRequest.enable` in the [HTTP Log Config File](#log-config-file) or `opts.HTTPUtil.DumpRequest.Enable` in `httplog.Opts` to true to enable logging the request via the [httputil.DumpRequest](https://golang.org/pkg/net/http/httputil/#DumpRequest) method. Nothing special here, really - just providing an easy way to turn this on or off.  Output typically looks like:\n\n```bash\nhttputil.DumpRequest output:\nPOST /api/v1/appuser HTTP/1.1\nHost: 127.0.0.1:8080\nAccept: */*\nAccept-Encoding: gzip, deflate\nCache-Control: no-cache\nConnection: keep-alive\nContent-Length: 129\nContent-Type: application/json\nPostman-Token: 6d1b2461-59e2-4c87-baf5-d8e64a93c55b\nUser-Agent: PostmanRuntime/7.1.1\n\n{\"username\": \"repoMan\",\"mobile_ID\": \"1-800-repoman\",\"email\":\"repoman@alwaysintense.com\",\"First_Name\":\"Otto\",\"Last_Name\":\"Maddox\"}\n```\n\n\u003eNOTE - in order to log the body, set `httputil.DumpRequest.body` in `httplogOpt.json` or `opts.HTTPUtil.DumpRequest.Enable` in `httplog.Opts` to true. If you don't want this data, set the appropriate field to false in the JSON config file (`httpLogOpt.json`) or `httplog.Opts` struct (depending on which method you chose).\n\n### Add Unique ID and Key Request Elements to Context\n\n**httplog** middleware creates a unique ID to track each request. In addition, it adds several request elements to the request context that can be accessed with helper functions later.\n\n#### Unique Request ID\n\nEach request is given a 20 character Unique Request ID generated by [xid](https://github.com/rs/xid). This unique ID is populated throughout each log type for easy tracking. This ID is also meant to be sent back to the client of your API either in the response header or response body (see [below](#retrieve-unique-id-and-key-request-elements-from-context) for further help on including httplog context items in a response body).\n\n#### Other Request Elements added to Context\n\nIn addition to the generated Unique ID, httplog also adds the following request elements to the context:\n\n- Host\n- Port\n- Path\n- Raw Query\n- Fragment\n\n### Retrieve Unique ID and Key Request Elements from Context\n\nIn order to retrieve particular key:value pairs from the request context, the following helper functions are provided:\n\n```go\n// RequestID gets the Request ID from the context.\nfunc RequestID(ctx context.Context) string {\n```\n\n```go\n// RequestHost gets the request host from the context\nfunc RequestHost(ctx context.Context) string {\n```\n\n```go\n// RequestPort gets the request port from the context\nfunc RequestPort(ctx context.Context) string {\n```\n\n```go\n// RequestPath gets the request URL from the context\nfunc RequestPath(ctx context.Context) string {\n```\n\n```go\n// RequestRawQuery gets the request Query string details from the context\nfunc RequestRawQuery(ctx context.Context) string {\n```\n\n```go\n// RequestFragment gets the request Fragment details from the context\nfunc RequestFragment(ctx context.Context) string {\n```\n\n### Audit Struct for Response Payload\n\nSome APIs may find it helpful to echo back certain request elements or helpful contextual information in the response payload. **httplog** provides [httplog.Audit](https://godoc.org/github.com/gilcrest/httplog#Audit) for just this purpose. Use constructor function `httplog.NewAudit` to initialize this struct. The unique Request ID will always be sent back as part of the struct -- the other request elements are optional and can be turned on/off using the `httplog.AuditOpts` config struct. Below is a sample response with the audit struct included to give an idea of how it can be used. The example below is from the [go-API-template](https://github.com/gilcrest/go-API-template) repository which has examples of this audit struct in use.\n\n```json\n{\n    \"username\": \"15\",\n    \"mobile_id\": \"1-800-repoman\",\n    \"email\": \"repoman@alwaysintense.com\",\n    \"first_name\": \"Otto\",\n    \"last_name\": \"Maddox\",\n    \"update_user_id\": \"chillcrest\",\n    \"created\": 1539138260,\n    \"audit\": {\n        \"id\": \"beum5l708qml02e3hvag\",\n        \"url\": {\n            \"host\": \"127.0.0.1\",\n            \"port\": \"8080\",\n            \"path\": \"/api/v1/adapter/user\",\n            \"query\": \"qskey1=fake\u0026qskey2=test\"\n        }\n    }\n}\n```\n\nA snippet from the `handleUserCreate` handler function within [go-API-template](https://github.com/gilcrest/go-API-template) shows how to setup the `AuditOpts` struct and turn on a few options as well as plugging `httplog.Audit` into the response.\n\n```go\n    // create new AuditOpts struct and set options to true that you\n    // want to see in the response body (Request ID is always present)\n    aopt := new(httplog.AuditOpts)\n    aopt.Host = true\n    aopt.Port = true\n    aopt.Path = true\n    aopt.Query = true\n\n    // get a new httplog.Audit struct from NewAudit using the\n    // above set options and request context\n    aud, err := httplog.NewAudit(ctx, aopt)\n    if err != nil {\n        err = HTTPErr{\n            Code: http.StatusInternalServerError,\n            Kind: errs.Other,\n            Err:  err,\n        }\n        httpError(w, err)\n        return\n    }\n\n    // create a new response struct and set Audit and other\n    // relevant elements\n    resp := new(response)\n    resp.Audit = aud\n    resp.Username = usr.Username()\n    resp.MobileID = usr.MobileID()\n    resp.Email = usr.Email()\n    resp.FirstName = usr.FirstName()\n    resp.LastName = usr.LastName()\n    resp.UpdateUserID = usr.UpdateUserID()\n    resp.UpdateUnixTime = usr.UpdateTimestamp().Unix()\n\n    // Encode response struct to JSON for the response body\n    json.NewEncoder(w).Encode(*resp)\n```\n\n----\n\n### Helpful Resources I've used in this library (outside of the standard, yet amazing blog.golang.org and golang.org/doc/, etc.)\n\nwebsites/youtube\n\n- [JustforFunc](https://www.youtube.com/channel/UC_BzFbxG2za3bp5NRRRXJSw)\n- [Go By Example](https://gobyexample.com/)\n\nBooks\n\n- [Go in Action](https://www.amazon.com/Go-Action-William-Kennedy/dp/1617291781)\n- [The Go Programming Language](https://www.amazon.com/Programming-Language-Addison-Wesley-Professional-Computing/dp/0134190440/ref=pd_lpo_sbs_14_t_0?_encoding=UTF8\u0026psc=1\u0026refRID=P9Z5CJMV36NXRZNXKG1F)\n\nBlog/Medium Posts\n\n- [How I write Go HTTP services after seven years](https://medium.com/statuscode/how-i-write-go-http-services-after-seven-years-37c208122831)\n- [The http Handler Wrapper Technique in #golang, updated -- by Mat Ryer](https://medium.com/@matryer/the-http-handler-wrapper-technique-in-golang-updated-bc7fbcffa702)\n- [Writing middleware in #golang and how Go makes it so much fun. -- by Mat Ryer](https://medium.com/@matryer/writing-middleware-in-golang-and-how-go-makes-it-so-much-fun-4375c1246e81)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgilcrest%2Fhttplog","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgilcrest%2Fhttplog","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgilcrest%2Fhttplog/lists"}