Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions src/logs/log-aggregator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,110 @@ describe('log-aggregator', () => {
expect(stats.byDomain.has('-')).toBe(true);
expect(stats.byDomain.has('github.com')).toBe(true);
});

it('should filter out transaction-end-before-headers entries', () => {
const entries: ParsedLogEntry[] = [
createLogEntry({
domain: 'github.com',
url: 'github.com:443',
isAllowed: true
}),
createLogEntry({
domain: '-',
url: 'error:transaction-end-before-headers',
decision: 'NONE_NONE:HIER_NONE',
statusCode: 0,
isAllowed: false
}),
createLogEntry({
domain: 'npmjs.org',
url: 'npmjs.org:443',
isAllowed: true
}),
];

const stats = aggregateLogs(entries);

// Should only count the two valid entries
expect(stats.totalRequests).toBe(2); // Only actual requests, not benign operational entries
expect(stats.allowedRequests).toBe(2);
expect(stats.deniedRequests).toBe(0);
expect(stats.uniqueDomains).toBe(2);
expect(stats.byDomain.has('github.com')).toBe(true);
expect(stats.byDomain.has('npmjs.org')).toBe(true);
expect(stats.byDomain.has('-')).toBe(false); // Filtered entry not in domain stats
});

it('should handle multiple transaction-end-before-headers entries', () => {
const entries: ParsedLogEntry[] = [
createLogEntry({
domain: 'github.com',
url: 'github.com:443',
isAllowed: true
}),
createLogEntry({
domain: '-',
url: 'error:transaction-end-before-headers',
clientIp: '::1', // healthcheck from localhost
decision: 'NONE_NONE:HIER_NONE',
statusCode: 0,
isAllowed: false
}),
createLogEntry({
domain: '-',
url: 'error:transaction-end-before-headers',
clientIp: '172.30.0.20', // shutdown-time connection closure
decision: 'NONE_NONE:HIER_NONE',
statusCode: 0,
isAllowed: false
}),
createLogEntry({
domain: 'npmjs.org',
url: 'npmjs.org:443',
isAllowed: true
}),
];

const stats = aggregateLogs(entries);

expect(stats.totalRequests).toBe(2); // Only actual requests
expect(stats.allowedRequests).toBe(2);
expect(stats.deniedRequests).toBe(0);
expect(stats.uniqueDomains).toBe(2);
});

it('should still count time range from all entries including filtered ones', () => {
const entries: ParsedLogEntry[] = [
createLogEntry({
timestamp: 1000.0,
domain: 'github.com',
url: 'github.com:443',
isAllowed: true
}),
createLogEntry({
timestamp: 1500.0,
domain: '-',
url: 'error:transaction-end-before-headers',
decision: 'NONE_NONE:HIER_NONE',
statusCode: 0,
isAllowed: false
}),
createLogEntry({
timestamp: 2000.0,
domain: 'npmjs.org',
url: 'npmjs.org:443',
isAllowed: true
}),
];

const stats = aggregateLogs(entries);

// Time range should span all entries, even filtered ones
expect(stats.timeRange).toEqual({
start: 1000.0,
end: 2000.0,
});
});
});

describe('loadAllLogs', () => {
Expand Down
13 changes: 11 additions & 2 deletions src/logs/log-aggregator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,26 @@ export function aggregateLogs(entries: ParsedLogEntry[]): AggregatedStats {
let deniedRequests = 0;
let minTimestamp = Infinity;
let maxTimestamp = -Infinity;
let totalRequests = 0;

for (const entry of entries) {
// Track time range
// Track time range for all entries
if (entry.timestamp < minTimestamp) {
minTimestamp = entry.timestamp;
}
if (entry.timestamp > maxTimestamp) {
maxTimestamp = entry.timestamp;
}

// Skip benign operational entries (connection closures without HTTP headers)
// These appear during healthchecks and shutdown-time keep-alive connection closures
if (entry.url === 'error:transaction-end-before-headers') {
continue;
}

// Count this as a real request
totalRequests++;

// Count allowed/denied
if (entry.isAllowed) {
allowedRequests++;
Expand Down Expand Up @@ -91,7 +101,6 @@ export function aggregateLogs(entries: ParsedLogEntry[]): AggregatedStats {
}
}

const totalRequests = entries.length;
const uniqueDomains = byDomain.size;
const timeRange =
entries.length > 0 ? { start: minTimestamp, end: maxTimestamp } : null;
Expand Down
26 changes: 26 additions & 0 deletions src/squid-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,32 @@ describe('generateSquidConfig', () => {
const result = generateSquidConfig(config);
expect(result).toContain('access_log /var/log/squid/access.log firewall_detailed');
});

it('should filter localhost healthcheck probes from logs', () => {
const config: SquidConfig = {
domains: ['example.com'],
port: defaultPort,
};
const result = generateSquidConfig(config);
expect(result).toContain('acl healthcheck_localhost src 127.0.0.1 ::1');
expect(result).toContain('log_access deny healthcheck_localhost');
});

it('should place healthcheck filter before access_log directive', () => {
const config: SquidConfig = {
domains: ['example.com'],
port: defaultPort,
};
const result = generateSquidConfig(config);
// Verify the order: ACL definition, then log_access deny, then access_log
const aclIndex = result.indexOf('acl healthcheck_localhost');
const logAccessIndex = result.indexOf('log_access deny healthcheck_localhost');
const accessLogIndex = result.indexOf('access_log /var/log/squid/access.log');

expect(aclIndex).toBeGreaterThan(-1);
expect(logAccessIndex).toBeGreaterThan(aclIndex);
expect(accessLogIndex).toBeGreaterThan(logAccessIndex);
});
});

describe('Streaming/Long-lived Connection Support', () => {
Expand Down
4 changes: 4 additions & 0 deletions src/squid-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,10 @@ pinger_enable off
# Note: For CONNECT requests (HTTPS), the domain is in the URL field
logformat firewall_detailed %ts.%03tu %>a:%>p %{Host}>h %<a:%<p %rv %rm %>Hs %Ss:%Sh %ru "%{User-Agent}>h"

# Don't log healthcheck probes from localhost
acl healthcheck_localhost src 127.0.0.1 ::1
log_access deny healthcheck_localhost

# Access log and cache configuration
access_log /var/log/squid/access.log firewall_detailed
cache_log /var/log/squid/cache.log
Expand Down
Loading