{"id":3639,"date":"2026-10-14T14:55:07","date_gmt":"2026-10-14T06:55:07","guid":{"rendered":"http:\/\/www.mayepcamviens150.com\/blog\/?p=3639"},"modified":"2026-10-14T14:55:07","modified_gmt":"2026-10-14T06:55:07","slug":"how-to-use-winston-for-logging-in-express-485c-b40091","status":"publish","type":"post","link":"http:\/\/www.mayepcamviens150.com\/blog\/2026\/10\/14\/how-to-use-winston-for-logging-in-express-485c-b40091\/","title":{"rendered":"How to use Winston for logging in Express?"},"content":{"rendered":"<p>Hey folks, if you\u2019re running an Express-based shop and still using those basic <code>console.log<\/code> statements for your app logs, you\u2019re probably dealing with a mess right now. I get it\u2014when you\u2019re cranking out features and trying to hit deadlines, reaching for <code>console.log<\/code> feels like the fastest move. But let\u2019s be real: when your production app starts acting weird at 2 a.m., scrolling through thousands of garbled text lines (with no timestamps, no context, and no way to filter errors from regular requests) is the worst. That\u2019s where Winston comes in. As an Express solutions provider that\u2019s built, scaled, and troubleshooted 50+ Express apps for clients, I swear by Winston for logging. Today I\u2019m breaking down exactly how to set it up, tweak it to fit your workflow, and stop pulling your hair over unhelpful logs. <a href=\"https:\/\/www.fsshijielogistics.com\/express\/\">Express<\/a><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.fsshijielogistics.com\/uploads\/44940\/small\/express-ups-to-canadaed62d.jpg\"><\/p>\n<h3>First Things First: Why Ditch Console.log for Winston?<\/h3>\n<p>Wait, hold up\u2014before we jump into code, let\u2019s talk about why Winston\u2019s worth the tiny bit of setup it takes. Console.log is like leaving sticky notes on your office wall to track work; sure, it works for quick tasks, but once you hit 10,000 notes, you can\u2019t find anything. Winston is your organized filing system for logs. Let\u2019s list the real wins (pun intended) that matter for Express devs:<\/p>\n<ul>\n<li><strong>Proper log levels<\/strong>: Winston lets you separate <code>error<\/code>, <code>warn<\/code>, <code>info<\/code>, <code>http<\/code>, and <code>debug<\/code> logs. So you don\u2019t have to guess if a random line is a critical crash or just a normal GET request.<\/li>\n<li><strong>Structured logging (JSON, mostly)<\/strong>: Your logs are machine-readable, so you can plug them into tools like Datadog or Elasticsearch later without parsing garbage text.<\/li>\n<li><strong>Multiple outputs<\/strong>: You can send logs to the console for local dev, a file for production debugging, or even external services like Sentry without writing custom code every time.<\/li>\n<li><strong>Timestamps and context by default<\/strong>: No more adding <code>new Date()<\/code> to every log line manually. Winston will attach timestamps, request IDs, or even user IDs automatically if you set it up right.<br \/>\nFor our Express clients, we switched from console.log to Winston on average 2 years ago, and our post-launch troubleshooting time dropped by 40%\u2014no exaggeration.<\/li>\n<\/ul>\n<h3>Step 1: Install the Good Stuff<\/h3>\n<p>Alright, let\u2019s get this set up. This is for Express 4.x, since that\u2019s still the most widely used version (most of our clients haven\u2019t jumped to 5.x yet without a good reason). First, open your terminal in your Express project folder. You only need two main packages: <code>winston<\/code> itself, and the Express middleware that ties it all together, called <code>express-winston<\/code>. That middleware lets you log every incoming request, response, and even errors right in your Express pipeline.<br \/>\nRun this npm install command\u2014simple, no gotchas here:<\/p>\n<pre><code class=\"language-bash\">npm install winston express-winston\n<\/code><\/pre>\n<p>Wait, quick pro tip: if you\u2019re using TypeScript, add <code>@types\/winston<\/code> and <code>@types\/express-winston<\/code> too, but 90% of our clients still use plain JS, so we\u2019ll stick to that today.<\/p>\n<h3>Step 2: Set Up the Winston Logger Basics<\/h3>\n<p>Now let\u2019s make a logger file. We don\u2019t want to bloat our <code>app.js<\/code> or <code>index.js<\/code> with logger code, so create a new folder called <code>utils<\/code> (most Express projects have this) and add a file called <code>logger.js<\/code>. Here\u2019s where we configure Winston to work for us, not against us.<br \/>\nLet\u2019s start with the core config. We\u2019ll use two \u201ctransports\u201d (that\u2019s Winston lingo for where logs go): the console for local dev, and a daily rotating file for production. Why daily? Because if you log every request to a single file, after a month it\u2019ll be 10GB+ and impossible to parse. The <code>winston-daily-rotate-file<\/code> package handles that, so install that too:<\/p>\n<pre><code class=\"language-bash\">npm install winston-daily-rotate-file\n<\/code><\/pre>\n<p>Now, open <code>utils\/logger.js<\/code> and paste this. I\u2019ll explain each part so you don\u2019t just copy-paste blind:<\/p>\n<pre><code class=\"language-javascript\">const { createLogger, format, transports } = require('winston');\nconst DailyRotateFile = require('winston-daily-rotate-file');\nconst { combine, timestamp, printf, json } = format;\n\n\/\/ Custom log format for console (easier to read locally)\nconst consoleFormat = printf(({ level, message, timestamp, ...meta }) =&gt; {\n  return `${timestamp} [${level.toUpperCase()}]: ${message} ${Object.keys(meta).length ? JSON.stringify(meta, null, 2) : ''}`;\n});\n\n\/\/ Build the actual logger\nconst logger = createLogger({\n  \/\/ Log levels\u2014we'll use the standard npm levels here\n  level: process.env.NODE_ENV === 'production' ? 'http' : 'debug',\n  format: combine(timestamp(), json()), \/\/ JSON for files, structured for console\n  transports: [\n    \/\/ Console transport for local dev\n    new transports.Console({ format: combine(timestamp({ format: 'HH:mm:ss' }), consoleFormat) }),\n    \/\/ Daily rotating file for production logs\n    new DailyRotateFile({\n      filename: 'logs\/app-%DATE%.log',\n      datePattern: 'YYYY-MM-DD',\n      maxSize: '20m', \/\/ Split files at 20MB so they're manageable\n      maxFiles: '14d', \/\/ Delete logs older than 14 days to save space\n      level: 'http' \/\/ Only log http and above to the file (no debug noise)\n    })\n  ],\n  \/\/ Don't exit on uncaught exceptions\u2014let Express handle that\n  exitOnError: false\n});\n\nmodule.exports = logger;\n<\/code><\/pre>\n<p>Wait, why the different formats? Local dev doesn\u2019t need giant JSON blobs you have to parse in your head\u2014so we use a human-friendly printf format for the console. Production needs JSON so your monitoring tool can pick it up, hence the json() format. And the log level thing: in local dev, we log everything (debug to error), but in production, we skip super noisy debug logs and only keep http (all requests) and higher, which is exactly what we need.<br \/>\nAlso, that DailyRotateFile config is non-negotiable for production. I\u2019ve seen clients use a single log file that got so big, their server\u2019s disk filled up and took down the whole app. Don\u2019t be that guy.<\/p>\n<h3>Step 3: Integrate with Express (The Important Part)<\/h3>\n<p>Okay, now we have a logger, but how do we make it log every Express request and error? That\u2019s where <code>express-winston<\/code> comes in. We don\u2019t want to manually add <code>logger.info('Incoming request')<\/code> to every route\u2014we want Express to handle it automatically.<br \/>\nFirst, head to your main Express file (usually <code>app.js<\/code> or <code>index.js<\/code>). You\u2019ll need to require two things: your custom logger, and <code>express-winston<\/code>. Here\u2019s what that part looks like:<\/p>\n<pre><code class=\"language-javascript\">const express = require('express');\nconst logger = require('.\/utils\/logger');\nconst { expressWinston } = require('express-winston');\n\nconst app = express();\n\n\/\/ Add the request logger middleware *BEFORE* your routes\napp.use(expressWinston.logger({\n  winstonInstance: logger, \/\/ Use our custom Winston setup\n  meta: true, \/\/ Attach extra metadata (like request IP, user agent)\n  msg: 'HTTP {{req.method}} {{req.url}}', \/\/ Custom message for requests\n  expressFormat: true, \/\/ Use Express's default request format for clarity\n  colorize: process.env.NODE_ENV === 'development' \/\/ Color logs in local console\n}));\n\n\/\/ Then all your actual routes go here\napp.get('\/', (req, res) =&gt; res.send('Hello World!'));\napp.get('\/api\/users\/:id', (req, res) =&gt; res.json({ id: req.params.id, name: 'Jake' }));\n\n\/\/ Add the error logger middleware *AFTER* your routes (and any error-handling middleware)\napp.use(expressWinston.errorLogger({\n  winstonInstance: logger\n}));\n\n\/\/ Start the server\nconst PORT = process.env.PORT || 3000;\napp.listen(PORT, () =&gt; logger.info(`Server running on port ${PORT}`));\n<\/code><\/pre>\n<p>Wait a second\u2014order matters here. The request logger has to go before your routes, otherwise it won\u2019t log requests. The error logger has to go after routes, so it can catch any errors that come up in your route handlers. That\u2019s a tiny detail that trips up so many devs when they first set this up. I\u2019ve seen at least 3 client projects where the error logger was in the wrong spot, so critical errors were just vanishing into thin air. Fix that, and you\u2019re already ahead of the game.<\/p>\n<h3>Step 4: Add Custom Logs for Your App-Specific Stuff<\/h3>\n<p>Okay, so now you\u2019ve got all Express requests and errors logged, but what about stuff unique to your app? Like if your user updates their profile, or a payment fails? You don\u2019t want those buried in the <code>http<\/code> logs. That\u2019s where you use the logger instance directly in your code.<br \/>\nLet\u2019s take a simple example: a user signup route. Here\u2019s how you\u2019d add a custom log for when a new user is created:<\/p>\n<pre><code class=\"language-javascript\">app.post('\/api\/auth\/signup', async (req, res) =&gt; {\n  try {\n    const newUser = await User.create(req.body);\n    \/\/ Log a custom info-level event with the user's ID and email\n    logger.info('New user signed up', { userId: newUser.id, email: newUser.email, event: 'user_signup' });\n    res.status(201).json(newUser);\n  } catch (err) {\n    \/\/ Log the error with extra context so you don't just see &quot;Database error&quot;\n    logger.error('User signup failed', { email: req.body.email, error: err.message, stack: err.stack });\n    res.status(500).json({ message: 'Signup failed, try again later' });\n  }\n});\n<\/code><\/pre>\n<p>See the context we\u2019re adding? Instead of just logging <code>err<\/code>, we\u2019re logging the email that tried to sign up, the error message, and even the stack trace. When we get a support ticket that says \u201cmy signup didn\u2019t work\u201d, we can check the logs and immediately see if it\u2019s a database issue, a duplicate email, or something else. That\u2019s the kind of detail that cuts support time in half.<\/p>\n<h3>Pro Tips to Tweak for Production (We\u2019ve Tested This)<\/h3>\n<p>Now that you\u2019ve got the basics down, let\u2019s add a few tweaks that we\u2019ve battle-tested on our client apps, because one size doesn\u2019t fit all.<br \/>\nFirst: Hide sensitive data. You don\u2019t want passwords, credit card numbers, or API keys showing up in your logs. We use the <code>blacklist<\/code> option in <code>express-winston<\/code> to strip those out. For example, add <code>blacklist: ['password', 'creditCard', 'apiKey']<\/code> to both the <code>expressWinston.logger<\/code> and <code>errorLogger<\/code> configs. That\u2019s non-negotiable\u2014GDPR and PCI will come for you if you log sensitive data.<br \/>\nSecond: Adjust log levels for production. Wait, we mentioned this earlier, but double-check: in production, set <code>level: 'http'<\/code> for the file transport. If you set it to <code>debug<\/code>, you\u2019ll end up with so many logs that your file storage fills up in days. For most Express apps, <code>http<\/code> (all requests) and <code>error<\/code> are more than enough.<br \/>\nThird: Add request IDs. When you\u2019re troubleshooting a specific user\u2019s request, being able to search logs for their request ID is a game-changer. You can generate a unique ID on every incoming request and add it to all logs for that request. Here\u2019s a quick way to do it with uuid:<br \/>\nInstall it: <code>npm install uuid<\/code><br \/>\nAdd this middleware before your request logger:<\/p>\n<pre><code class=\"language-javascript\">const { v4: uuidv4 } = require('uuid');\napp.use((req, res, next) =&gt; {\n  req.id = uuidv4();\n  next();\n});\n<\/code><\/pre>\n<p>Then update your custom logs to include <code>{ requestId: req.id }<\/code>, and <code>express-winston<\/code> will automatically pick up the <code>req.id<\/code> and add it to all request\/error logs. So you can search logs by <code>requestId<\/code> and see exactly what happened to that specific request, no scrolling needed.<\/p>\n<h3>Common Mistakes We See (Avoid These)<\/h3>\n<p>Let\u2019s wrap this up with the mistakes we\u2019ve seen devs make when setting up Winston for Express, because you don\u2019t have to learn them the hard way.<\/p>\n<ol>\n<li><strong>Forgetting transport levels<\/strong>: We had a client once that logged <code>debug<\/code> to their production file, and their server\u2019s disk got full in 3 days. Don\u2019t do that\u2014match transport levels to your environment.<\/li>\n<li><strong>Putting the error logger in the wrong spot<\/strong>: Like I said earlier, error logger has to go after routes. We fixed a client issue last month where they had it before routes, so all errors were being caught by their 404 handler instead of the error logger. Total chaos.<\/li>\n<li><strong>Not blacklisting sensitive data<\/strong>: Another client had a bug where they logged the <code>authorization<\/code> header, so we saw hundreds of API keys in their logs within hours of a tiny deployment. They had to rotate every key, which cost them $1,500 in downtime and dev time. Don\u2019t skip the blacklist.<\/li>\n<li><strong>Overcomplicating the format<\/strong>: You don\u2019t need 10 different custom formats. Start simple, use JSON for production, human-readable for local. Add custom context only when you need it.<\/li>\n<\/ol>\n<p>That\u2019s pretty much it! Winston isn\u2019t the fanciest logging tool out there, but for Express apps, it\u2019s the sweet spot between simplicity and power. We still use it for every single Express project we build, and every time a client comes to us with a logging headache from their old <code>console.log<\/code> setup, the first thing we do is switch them over to Winston.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.fsshijielogistics.com\/uploads\/44940\/small\/sea-freight-lcl-express-to-other-countries8c19b.jpg\"><\/p>\n<p>If you\u2019re an Express dev or a business that runs Express apps and you\u2019re tired of vague logs, late-night troubleshooting sessions, or production issues you can\u2019t track down, we can help. Whether you just need help setting up Winston for your existing app, or you want to build a new Express app with rock-solid logging from day one, our team has the expertise to get it right without the growing pains. Reach out to our team to connect and chat through your specific needs\u2014we\u2019re here to make logging (and your Express apps) way less painful.<\/p>\n<h3>References<\/h3>\n<p><a href=\"https:\/\/www.fsshijielogistics.com\/dropshipping\/dropshipping-alibaba\/\">Dropshipping Alibaba<\/a> Node.js Winston Official Documentation<br \/>\nExpress-Winston GitHub Repository<br \/>\nWinston Daily Rotate File Package Notes<br \/>\nExpress 4.x Middleware Order Guidelines<\/p>\n<hr>\n<p><a href=\"https:\/\/www.fsshijielogistics.com\/\">Foshan Shijie International Logistics Co., Ltd.<\/a><br \/>With abundant experience, we are one of the most professional express service suppliers in China. We are committed to offering reliable express service and logistics solutions with low price. If you have any enquiry about quote, please feel free to email us.<br \/>Address: Room 701A, Office Building, Jinbo Commercial Center, No. 88, Guiye Road, Guicheng Street, Nanhai District, Foshan City, Guangdong Province<br \/>E-mail: alicesmith.tradingbusiness@gmail.com<br \/>WebSite: <a href=\"https:\/\/www.fsshijielogistics.com\/\">https:\/\/www.fsshijielogistics.com\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Hey folks, if you\u2019re running an Express-based shop and still using those basic console.log statements for &hellip; <a title=\"How to use Winston for logging in Express?\" class=\"hm-read-more\" href=\"http:\/\/www.mayepcamviens150.com\/blog\/2026\/10\/14\/how-to-use-winston-for-logging-in-express-485c-b40091\/\"><span class=\"screen-reader-text\">How to use Winston for logging in Express?<\/span>Read more<\/a><\/p>\n","protected":false},"author":955,"featured_media":3639,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[3602],"class_list":["post-3639","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-industry","tag-express-4791-b4b12f"],"_links":{"self":[{"href":"http:\/\/www.mayepcamviens150.com\/blog\/wp-json\/wp\/v2\/posts\/3639","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/www.mayepcamviens150.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.mayepcamviens150.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.mayepcamviens150.com\/blog\/wp-json\/wp\/v2\/users\/955"}],"replies":[{"embeddable":true,"href":"http:\/\/www.mayepcamviens150.com\/blog\/wp-json\/wp\/v2\/comments?post=3639"}],"version-history":[{"count":0,"href":"http:\/\/www.mayepcamviens150.com\/blog\/wp-json\/wp\/v2\/posts\/3639\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"http:\/\/www.mayepcamviens150.com\/blog\/wp-json\/wp\/v2\/posts\/3639"}],"wp:attachment":[{"href":"http:\/\/www.mayepcamviens150.com\/blog\/wp-json\/wp\/v2\/media?parent=3639"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.mayepcamviens150.com\/blog\/wp-json\/wp\/v2\/categories?post=3639"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.mayepcamviens150.com\/blog\/wp-json\/wp\/v2\/tags?post=3639"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}