惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 【当耐特】
K
Kaspersky official blog
V
Vulnerabilities – Threatpost
Hacker News - Newest:
Hacker News - Newest: "LLM"
Security Archives - TechRepublic
Security Archives - TechRepublic
S
Secure Thoughts
I
Intezer
TaoSecurity Blog
TaoSecurity Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Spread Privacy
Spread Privacy
A
About on SuperTechFans
NISL@THU
NISL@THU
The GitHub Blog
The GitHub Blog
Hugging Face - Blog
Hugging Face - Blog
S
Security @ Cisco Blogs
S
SegmentFault 最新的问题
G
Google Developers Blog
B
Blog
N
News and Events Feed by Topic
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Google DeepMind News
Google DeepMind News
V2EX - 技术
V2EX - 技术
V
Visual Studio Blog
MyScale Blog
MyScale Blog
Webroot Blog
Webroot Blog
Vercel News
Vercel News
IT之家
IT之家
Microsoft Security Blog
Microsoft Security Blog
Last Week in AI
Last Week in AI
Y
Y Combinator Blog
S
Security Affairs
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Stack Overflow Blog
Stack Overflow Blog
P
Proofpoint News Feed
L
Lohrmann on Cybersecurity
博客园 - 叶小钗
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Know Your Adversary
Know Your Adversary
T
Tailwind CSS Blog
F
Fortinet All Blogs
D
DataBreaches.Net
博客园 - Franky
博客园_首页
H
Heimdal Security Blog
宝玉的分享
宝玉的分享
阮一峰的网络日志
阮一峰的网络日志
Attack and Defense Labs
Attack and Defense Labs
Project Zero
Project Zero
雷峰网
雷峰网

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Your PHP Logs are Lying to You
Mike Georgeff · 2026-06-16 · via DEV Community

The Evolution: From Log Tails to Indexed Search

Most teams start with a stream-based log platform, such as Papertrail, Loggly, or legacy syslog viewers. These platforms are fast to set up, and work well early on, but ultimately are just live tail with grep. Unstructured logs work fine here because a human is reading and searching them manually. As the application and team grow, you graduate to an indexed search platform, such as Elastic/OpenSearch, Datadog Logs, or Loki with LogQL. This is where unstructured logs break down because these platforms expect to index fields, not parse free text. You can still ship plain text to Elastic, but you've thrown away everything that makes it powerful: field filtering, aggregations, dashboards, and alerting on specific values. If you're thinking "we write to a log file, not a stream", that's fine, and many of these stream platforms are reading a file anyway. The live tail is literally tail -f. The problem was never the destination. The problem is what the file contains. The platform didn't change your logs, it exposed what your logs already were: unstructured text. Structured logs enable trend analysis: error rate over time, errors by application version, change failure rate after a deployment. These are questions grep can't answer. "Did our error rate increase after v2.3.0 shipped?" requires version, level, and timestamp as discrete, indexed fields you can aggregate on, not substrings buried in formatted strings. These are the kinds of questions that matter at an engineering org level. Change failure rate is a DORA metric. Teams measuring engineering effectiveness need their logs to support it, and that's only possible with consistent structure.

Stdout Decouples Your Application from the Log Destination

Writing to stdout is an intentional architectural boundary, not a lack of a "real" logging setup. Your application's only concern is emitting a well-formed log entry to the stream. Where it goes after that is not the application's problem. In a Kubernetes environment, an operator (Fluentd, Fluent Bit, Logstash, Vector) captures the stdout stream from each container and routes it. Swapping log platforms is an operator configuration change. Zero application code is touched, and there is zero redeployment of your software. A Kubernetes operator can fan logs out to multiple destinations simultaneously: Elastic for search indexing, S3 for long-term archival, PagerDuty for critical-level entries. Your application emits once, and the operator handles the rest. This fan-out is something a tightly coupled or file-based logger cannot do cleanly. The application would have to be aware of every destination. The 12-factor principle formalizes this: treat logs as event streams, let the execution environment handle routing and storage.

What Structured Logging Actually Is

Each log entry is a self-describing data structure, not a formatted string. Use NDJSON (Newline Delimited JSON), one JSON object per line, independently parseable by the logging platform. The platform indexes each field, not the full string. This is the difference between "find me logs containing the word payment" and "find me all error-level logs where amount > 100 in the last 15 minutes". The platform can only be as powerful as the structure you give it.

Example comparison:

# Unstructured
[2026-06-09 14:32:01] ERROR: Payment failed for user 4321

# Structured
{"timestamp":"2026-06-15T10:28:01.123+00:00","level":"error","severity":"error","message":"Payment failed","context":{"user_id":4321,"app_version":"1.3.0"}}

What belongs on every log line:

  • timestamp: RFC 3339 extended, UTC (e.g. 2026-06-10T14:32:01.123+00:00)
  • level: original PSR-3 level name, all 8 values are preserved
  • severity: reduced 5-value set (debug, info, warning, error, critical) aligned with structured log sinks
  • message: human-readable summary, kept short
  • context: structured key/value object, always present even when no context is supplied

Why level and severity? PSR-3 defines 8 levels, but most sinks don't natively understand all of them. severity gives the platform a normalized field it can reliably aggregate on without knowing PSR-3's model. In short, level preserves PSR-3 fidelity, severity serves the platform. No information is lost and the sink gets what it needs. The practical side effect is the severity field ends the "when do I use emergency vs alert vs critical" debate. Those distinctions came from syslog and don't map cleanly to application logging. Your alerting dashboard runs on severity. Operationally, all three mean the same thing. The nuance lives in level for whoever needs it.

Consistency matters more than completeness. Every line must have the same shape. A version field that appears on 60% of log lines is useless for change failure rate reporting.

PSR-3 and Why It Matters

PSR-3 is the PHP standard logging contract defining 8 severity methods and log(). Context, the second argument defined by the interface, is where the structure lives.

$logger->error('Payment failed', ['user_id' => 4321, 'app_version' => '1.3.0']);

user_id and app_version will appear on every "Payment failed" log, providing the logging platform consistent data to index.

Coding to PSR-3 means your application is decoupled from the concrete logger implementation. The decorator pattern is a natural extension of PSR-3 decoupling. Because LoggerInterface is an interface, you can wrap any logger that implements the same contract. A context-enriching logger accepts a LoggerInterface, enriches the context, then delegates to the inner logger. The call site never changes.

<?php

final class ContextEnrichingLogger implements LoggerInterface
{
    /**
     * @var ContextEnricher[]
     */
    private readonly array $enrichers;

    public function __construct(
        private readonly LoggerInterface $inner,
        ContextEnricher ...$enrichers
    ) {
        $this->enrichers = $enrichers;
    }

    public function info(string $message, array $context = []): void
    {
        $enrichedContext = $this->enrichContext($context);

        $this->inner->info($message, $enrichedContext);
    }

    // Remaining LoggerInterface methods
}

This is where consistent context attributes get added once, at the composition root, rather than at every log call site. Wire the decorator at boot time and every log call in the application gets the enriched context automatically. The enrichers can globally capture values such as app_version, environment, and correlation_id. This provides a central location to build out log context, eliminating repetition at every log call. What the enricher pipeline doesn't solve is converting a thrown exception into structured context.

Writing a Minimal Structured Logger

When writing a minimal logger, implement the LoggerInterface as a first-class citizen of the implementation, not an afterthought. The psr/log package also ships with a trait defining the 8 standard methods, allowing the implementation to focus on log().

<?php

use Psr\Log\LoggerTrait;
use Psr\Log\LoggerInterface;

final class Logger implements LoggerInterface
{
    use LoggerTrait;

}

Write the output to stdout.

/**
 * @var resource
 */
private $output;

public function __construct(mixed $output = STDOUT)
{
    if (!is_resource($output)) {
        throw new \InvalidArgumentException('Logger output must be a resource');
    }

    $this->output = $output;
}

Format each entry as a JSON object.

public function log(string $level, \Stringable|string $message, array $context = []): void
{
    $severityMap = [
        'emergency' => 'critical',
        'alert'     => 'critical',
        'critical'  => 'critical',
        'error'     => 'error',
        'warning'   => 'warning',
        'notice'    => 'info',
        'info'      => 'info',
        'debug'     => 'debug',
    ];

    $logEntry = [
        'timestamp' => (new \DateTimeImmutable())->format(\DateTimeInterface::RFC3339_EXTENDED),
        'level'     => $level,
        'severity'  => $severityMap[$level],
        'message'   => (string) $message,
        'context'   => $context,
    ];

    $json = json_encode($logEntry, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);

    fwrite($this->output, $json . PHP_EOL);
}

Keep it simple. No file rotation, no handlers, no formatters. The logger's only job is to serialize a log entry and write it to the stream.

What You Have Now and What's Still Missing

You have clean, machine-readable, streamable logs that log aggregators can ingest and index without configuration. Once your logs are structured, how do you enrich them with the proper context, especially when the event being logged is an exception? And that's before we even get to the thousands of JSON lines with no way to know which lines belong to the same request.

Part 2 of this series will cover the domain exception model and translation pipeline that turns exceptions into structured log data.


If you want to see a full concrete implementation of everything covered in this article, meritum/logger is a minimal PSR-3 structured logger built on these exact principles: RFC 3339 timestamps, NDJSON to stdout, severity normalization for structured log sinks.