{"course":{"slug":"building-apps-with-ai","title":"Building Apps with AI","url":"https://addaly.com/learn/building-apps-with-ai","level":"beginner","track":"build"},"lesson":{"slug":"printing-what-you-cannot-see","title":"Printing what you cannot see","url":"https://addaly.com/learn/building-apps-with-ai/printing-what-you-cannot-see","position":27,"of":87,"minutes":8,"answer":"Numbered prints of the actual values at each boundary locate a failure faster than reasoning about the code, and a console.log of a request body is how apps accidentally write passwords into their logs.","keyIdea":"Numbered prints of the actual values at each boundary locate a failure faster than reasoning about the code, and a console.log of a request body is how apps accidentally write passwords into their logs."},"body":{"format":"markdown","text":"## The oldest debugging tool still wins\n\nWhen an app does something you cannot explain, the fastest route is almost never a debugger. It is printing the values at three points and looking at what actually arrived.\n\n```js\nconsole.log('1 received:', body)\nconsole.log('2 after validation:', parsed)\nconsole.log('3 rows found:', existing.length)\n```\n\nNumber your prints. When the output stops at 2, you have located the failure to a range of about ten lines without reading any of them, and that beats an hour of reasoning about what the code ought to do.\n\n## Where the output goes\n\nThis catches everybody once. A `console.log` in browser code appears in the **browser console**. A `console.log` in server code appears in the **terminal running the server**, or in your host's log page in production. Same function, two different places, and looking in the wrong one produces the false conclusion that the code never ran.\n\nIf you are not sure which side a file runs on, print something distinctive and look in both.\n\n## Print the thing, not that you got there\n\n```js\nconsole.log('here')                    // useless\nconsole.log('slots', slots)            // better\nconsole.log('slots', JSON.stringify(slots, null, 2))  // best for objects\n```\n\nObjects logged plainly can print as `[Object]` or, in some browsers, show their state at the moment you expand them rather than the moment they were logged, which is confusing when the value changed in between. Stringifying takes a snapshot.\n\nAnd print the type when something is behaving oddly: `typeof value` distinguishes `\"30\"` from `30`, which is the cause of a whole family of arithmetic bugs where prices add up as text.\n\n## Four values worth printing at any boundary\n\nWhenever data crosses from one part of the system to another, print:\n\n- **What arrived**, before anything touches it.\n- **The identity in play**: which user the server believes is asking.\n- **What the query returned**, at least its length.\n- **What is being sent back.**\n\nMost bugs are a mismatch at one of those four points, not an error inside a function.\n\n## Moving from prints to logs\n\n`console.log` is for the next twenty minutes. For anything that will run in production, you want lines that are searchable and safe:\n\n```js\nconsole.log(JSON.stringify({\n  event: 'booking.rejected',\n  reason: 'overlap',\n  stylistId,\n  startsAt,\n  requestId,\n}))\n```\n\nOne line, structured, greppable. Every host's log viewer can search text, so `booking.rejected` finds every occurrence across a week. A `requestId` generated at the start of each request and included everywhere lets you follow one user's journey through interleaved output, which is the difference between logs you can use and logs you scroll past.\n\n## What must never be logged\n\nThis matters more than it sounds, because logs are stored, backed up, shared with your host, and sometimes readable by anybody with access to your dashboard.\n\nNever log passwords, session tokens, API keys, full card numbers, one-time codes, or complete request bodies containing personal data. Log an id rather than a person.\n\nA `console.log(req.body)` on a signup route writes your users' passwords into your hosting provider's log storage in plain text. This is not hypothetical; it is one of the more common ways small apps leak credentials, and it is always accidental.\n\n## Clean up, deliberately\n\nBefore committing, search for `console.log` and decide about each one. Some become real structured logs. The rest go.\n\n```bash\ngrep -rn \"console.log\" --include=\"*.ts\" --include=\"*.tsx\" src app lib\n```\n\nAgents leave these behind constantly. They are noise, they slow things down slightly, and occasionally one of them is printing something it should not.\n\n## The debugger, briefly\n\nYour editor can pause execution and let you inspect every variable at that moment. It is better than printing for a complicated bug, and it is worth an hour of your time eventually: in VS Code, a breakpoint is a click in the margin.\n\nBut do not let anybody tell you printing is unprofessional. Working programmers print values constantly, because for most problems it is faster, it works identically on your laptop and on a production server, and it leaves a record you can read afterwards."},"check":{"question":"A print statement added to a server route never appears anywhere. What is the most likely explanation?","options":[{"text":"The route is not being called, or you are looking in the browser console instead of the server output.","correct":true},{"text":"Production builds strip console.log statements automatically.","correct":false,"tempting":"Some bundlers do this for browser code, which is not where server output goes, and it would not apply while developing."},{"text":"The value being printed is undefined, so nothing is written.","correct":false,"tempting":"Undefined prints as the word undefined; a log line with no value still produces a line."},{"text":"Logging is disabled until a logging library is configured.","correct":false,"tempting":"Assumes a framework gate that does not exist; console output works with no setup on every runtime here."}]},"prev":{"slug":"the-smallest-experiment","title":"The smallest experiment that settles it","url":"https://addaly.com/learn/building-apps-with-ai/the-smallest-experiment"},"next":{"slug":"when-the-model-breaks-your-app","title":"When the model breaks your app","url":"https://addaly.com/learn/building-apps-with-ai/when-the-model-breaks-your-app"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'Printing what you cannot see', in Building Apps with AI. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/building-apps-with-ai/printing-what-you-cannot-see (Accessed: 13 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"Printing what you cannot see","courseTitle":"Building Apps with AI","module":{"index":3,"slug":"reading-and-checking","title":"Reading and checking what came back"},"answer":null,"keyIdea":"Numbered prints of the actual values at each boundary locate a failure faster than reasoning about the code, and a console.log of a request body is how apps accidentally write passwords into their logs.","hero":{"kind":"plate","image":null,"alt":null,"caption":null},"blocks":[{"t":"h2","id":"the-oldest-debugging-tool-still-wins","runs":[{"text":"The oldest debugging tool still wins"}]},{"t":"p","runs":[{"text":"When an app does something you cannot explain, the fastest route is almost never a debugger. It is printing the values at three points and looking at what actually arrived."}]},{"t":"code","lang":"js","label":"JavaScript","text":"console.log('1 received:', body)\nconsole.log('2 after validation:', parsed)\nconsole.log('3 rows found:', existing.length)"},{"t":"p","runs":[{"text":"Number your prints. When the output stops at 2, you have located the failure to a range of about ten lines without reading any of them, and that beats an hour of reasoning about what the code ought to do."}]},{"t":"h2","id":"where-the-output-goes","runs":[{"text":"Where the output goes"}]},{"t":"p","runs":[{"text":"This catches everybody once. A "},{"text":"console.log","code":true},{"text":" in browser code appears in the "},{"text":"browser console","b":true},{"text":". A "},{"text":"console.log","code":true},{"text":" in server code appears in the "},{"text":"terminal running the server","b":true},{"text":", or in your host’s log page in production. Same function, two different places, and looking in the wrong one produces the false conclusion that the code never ran."}]},{"t":"p","runs":[{"text":"If you are not sure which side a file runs on, print something distinctive and look in both."}]},{"t":"h2","id":"print-the-thing-not-that-you-got-there","runs":[{"text":"Print the thing, not that you got there"}]},{"t":"code","lang":"js","label":"JavaScript","text":"console.log('here')                    // useless\nconsole.log('slots', slots)            // better\nconsole.log('slots', JSON.stringify(slots, null, 2))  // best for objects"},{"t":"p","runs":[{"text":"Objects logged plainly can print as "},{"text":"[Object]","code":true},{"text":" or, in some browsers, show their state at the moment you expand them rather than the moment they were logged, which is confusing when the value changed in between. Stringifying takes a snapshot."}]},{"t":"p","runs":[{"text":"And print the type when something is behaving oddly: "},{"text":"typeof value","code":true},{"text":" distinguishes "},{"text":"\"30\"","code":true},{"text":" from "},{"text":"30","code":true},{"text":", which is the cause of a whole family of arithmetic bugs where prices add up as text."}]},{"t":"h2","id":"four-values-worth-printing-at-any-boundary","runs":[{"text":"Four values worth printing at any boundary"}]},{"t":"p","runs":[{"text":"Whenever data crosses from one part of the system to another, print:"}]},{"t":"ul","items":[[{"text":"What arrived","b":true},{"text":", before anything touches it."}],[{"text":"The identity in play","b":true},{"text":": which user the server believes is asking."}],[{"text":"What the query returned","b":true},{"text":", at least its length."}],[{"text":"What is being sent back.","b":true}]]},{"t":"p","runs":[{"text":"Most bugs are a mismatch at one of those four points, not an error inside a function."}]},{"t":"h2","id":"moving-from-prints-to-logs","runs":[{"text":"Moving from prints to logs"}]},{"t":"p","runs":[{"text":"console.log","code":true},{"text":" is for the next twenty minutes. For anything that will run in production, you want lines that are searchable and safe:"}]},{"t":"code","lang":"js","label":"JavaScript","text":"console.log(JSON.stringify({\n  event: 'booking.rejected',\n  reason: 'overlap',\n  stylistId,\n  startsAt,\n  requestId,\n}))"},{"t":"p","runs":[{"text":"One line, structured, greppable. Every host’s log viewer can search text, so "},{"text":"booking.rejected","code":true},{"text":" finds every occurrence across a week. A "},{"text":"requestId","code":true},{"text":" generated at the start of each request and included everywhere lets you follow one user’s journey through interleaved output, which is the difference between logs you can use and logs you scroll past."}]},{"t":"h2","id":"what-must-never-be-logged","runs":[{"text":"What must never be logged"}]},{"t":"p","runs":[{"text":"This matters more than it sounds, because logs are stored, backed up, shared with your host, and sometimes readable by anybody with access to your dashboard."}]},{"t":"p","runs":[{"text":"Never log passwords, session tokens, API keys, full card numbers, one-time codes, or complete request bodies containing personal data. Log an id rather than a person."}]},{"t":"p","runs":[{"text":"A "},{"text":"console.log(req.body)","code":true},{"text":" on a signup route writes your users’ passwords into your hosting provider’s log storage in plain text. This is not hypothetical; it is one of the more common ways small apps leak credentials, and it is always accidental."}]},{"t":"h2","id":"clean-up-deliberately","runs":[{"text":"Clean up, deliberately"}]},{"t":"p","runs":[{"text":"Before committing, search for "},{"text":"console.log","code":true},{"text":" and decide about each one. Some become real structured logs. The rest go."}]},{"t":"code","lang":"bash","label":"Bash","text":"grep -rn \"console.log\" --include=\"*.ts\" --include=\"*.tsx\" src app lib"},{"t":"p","runs":[{"text":"Agents leave these behind constantly. They are noise, they slow things down slightly, and occasionally one of them is printing something it should not."}]},{"t":"h2","id":"the-debugger-briefly","runs":[{"text":"The debugger, briefly"}]},{"t":"p","runs":[{"text":"Your editor can pause execution and let you inspect every variable at that moment. It is better than printing for a complicated bug, and it is worth an hour of your time eventually: in VS Code, a breakpoint is a click in the margin."}]},{"t":"p","runs":[{"text":"But do not let anybody tell you printing is unprofessional. Working programmers print values constantly, because for most problems it is faster, it works identically on your laptop and on a production server, and it leaves a record you can read afterwards."}]}],"check":{"question":[{"text":"A print statement added to a server route never appears anywhere. What is the most likely explanation?"}],"options":[{"text":[{"text":"The route is not being called, or you are looking in the browser console instead of the server output."}],"correct":true,"tempting":null},{"text":[{"text":"Production builds strip console.log statements automatically."}],"correct":false,"tempting":[{"text":"Some bundlers do this for browser code, which is not where server output goes, and it would not apply while developing."}]},{"text":[{"text":"The value being printed is undefined, so nothing is written."}],"correct":false,"tempting":[{"text":"Undefined prints as the word undefined; a log line with no value still produces a line."}]},{"text":[{"text":"Logging is disabled until a logging library is configured."}],"correct":false,"tempting":[{"text":"Assumes a framework gate that does not exist; console output works with no setup on every runtime here."}]}],"order":[1,0,2,3],"why":null},"prev":{"slug":"the-smallest-experiment","title":"The smallest experiment that settles it","minutes":8},"next":{"slug":"when-the-model-breaks-your-app","title":"When the model breaks your app","minutes":8}}}