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

推荐订阅源

大猫的无限游戏
大猫的无限游戏
阮一峰的网络日志
阮一峰的网络日志
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
WordPress大学
WordPress大学
博客园 - 叶小钗
Hugging Face - Blog
Hugging Face - Blog
博客园 - 司徒正美
Last Week in AI
Last Week in AI
罗磊的独立博客
量子位
Jina AI
Jina AI
T
Tailwind CSS Blog
Apple Machine Learning Research
Apple Machine Learning Research
IT之家
IT之家
美团技术团队
雷峰网
雷峰网
爱范儿
爱范儿
S
SegmentFault 最新的问题
小众软件
小众软件
月光博客
月光博客
酷 壳 – CoolShell
酷 壳 – CoolShell
人人都是产品经理
人人都是产品经理
The Cloudflare Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报

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
PHP analytics without cookies or database — and without v...
Odilon HUGON · 2026-05-09 · via DEV Community

Odilon HUGONNOT

I wanted to know which pages of my portfolio are visited, where the traffic comes from, and whether anyone actually reads the blog — without plugging in Google Analytics, without setting up a database, without displaying a cookie banner. The constraint: Apache + pure PHP hosting, nothing else.

Why not Google Analytics

GA4 would have taken 5 minutes. But two concrete problems:

  • GDPR: the moment you use GA, personal data (IP, behavior) goes to Google. Legal obligation to display a consent banner, handle refusals, maintain a processing register. For a personal portfolio, that's disproportionate.
  • Blocked: uBlock Origin, Brave, Firefox Enhanced Tracking Protection — GA is blocked by a significant portion of developers who are precisely my audience. The stats would be underreported.

Self-hosted alternatives like Umami or Plausible require Node.js + PostgreSQL. Not available here.

The solution: file log, hashed IP, zero cookies

A tracker.php file included at the top of each page. It writes a line to logs/visits.tsv on every visit. That's it.

The real value is in the security decisions:

1. No cookies set — therefore no consent required

GDPR mandates consent only when storing personal data on the user's side (cookies) or when identifying a natural person. None of that here. The server records a line and that's it — the user is unaware, feels nothing, and it's legal.

2. IP anonymized before storage

An IP address is personal data under GDPR. We never store it in plain text. Instead, we compute a salted SHA-256 hash, truncated to 16 characters:

$ip_hash = substr(
    hash('sha256', ($_SERVER['REMOTE_ADDR'] ?? '') . 'cv_salt_xK9!2026'),
    0, 16
);

Enter fullscreen mode Exit fullscreen mode

The salt makes the hash irreversible even with a rainbow table. The result allows counting unique visitors without ever being able to recover the original IP. This is exactly what France's CNIL recommends for the consent exemption.

3. Bot filtering upfront

Without filtering, the log file grows quickly with useless traffic — Google crawlers, Bing, scrapers, monitoring tools. A regex on the User-Agent eliminates the majority:

if (preg_match('/bot|crawl|spider|slurp|wget|curl|libwww|python|java|Go-http/i', $ua)) {
    return; // Exit immediately, nothing is logged
}

Enter fullscreen mode Exit fullscreen mode

This doesn't catch bots that disguise themselves as humans, but it covers 95% of real automated traffic.

4. Logs directory inaccessible from the web

The visits.tsv file contains IP hashes, URLs, and timestamps. Even anonymized, it must not be publicly accessible. A .htaccess in the logs/ directory is sufficient:

Deny from all

Enter fullscreen mode Exit fullscreen mode

Apache blocks any HTTP request to this directory. PHP on the server can still write to it internally — only browser access is blocked.

5. Isolated variables to avoid polluting the global scope

The tracker is included via require in index.php, so it shares the same PHP scope. To avoid overwriting existing variables or exposing internal data, all tracker variables are prefixed with $_ and deleted with unset() at the end of the script. It's rudimentary but effective without having to encapsulate everything in a function.

The dashboard

A password-protected stats.php reads the TSV file, calculates KPIs, and displays them with Chart.js (CDN). No complex persistent session — just a standard PHP $_SESSION and a plaintext password comparison (acceptable for a purely local/admin access on a personal portfolio).

KPIs displayed:

  • Total visits and unique visitors over 7 / 30 / 90 days
  • Visits today vs yesterday
  • Visits-per-day chart
  • Top pages visited
  • Traffic sources (direct / external / internal navigation)
  • Desktop / mobile / tablet breakdown
  • Hourly visit heatmap

What it doesn't do

No heatmaps, no funnels, no reconstructed sessions, no visit duration. For a portfolio, that level of detail is pointless. What matters: are people arriving, and which pages are they looking at.

The log file will grow. It will need periodic purging or log rotation (one file per month, for example). For now, a 100,000-line log weighs about 8 MB — PHP reads it in under a second.

Result

Zero cookies. Zero consent banner. Zero external production dependencies. Zero identifiable personal data stored. Functional dashboard in under 200 lines of PHP.

That's the right level of complexity for the actual need.

[

stats.php dashboard — KPIs, daily visits chart, top pages, traffic sources and hourly heatmap

stats.php in production — KPIs, visits/day, top pages, devices, hourly heatmap. Click to enlarge.

](https://www.web-developpeur.com/assets/images/stats.jpg "View full size")