...
This commit is contained in:
15
src/DEBIAN/control
Normal file
15
src/DEBIAN/control
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
Package: zabbix-php-monitoring
|
||||||
|
Version: 1.0.0
|
||||||
|
Section: admin
|
||||||
|
Priority: optional
|
||||||
|
Architecture: all
|
||||||
|
Depends: php8.3-fpm, zabbix-agent, logrotate
|
||||||
|
Maintainer: System Administrator <admin@localhost>
|
||||||
|
Description: PHP Exception Monitoring for Zabbix
|
||||||
|
This package provides comprehensive PHP exception monitoring for Zabbix.
|
||||||
|
It includes:
|
||||||
|
- Automatic exception logging via auto_prepend_file
|
||||||
|
- Zabbix template for monitoring and alerting
|
||||||
|
- Log rotation configuration
|
||||||
|
- User permissions for log access and service management
|
||||||
|
- Deduplication of identical exceptions based on unique identifiers
|
||||||
34
src/DEBIAN/postinst
Executable file
34
src/DEBIAN/postinst
Executable file
@@ -0,0 +1,34 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Post-installation script for zabbix-php-monitoring
|
||||||
|
|
||||||
|
# Create log directory with proper permissions
|
||||||
|
mkdir -p /var/log/zabbix
|
||||||
|
chown www-data:zabbix /var/log/zabbix
|
||||||
|
chmod 755 /var/log/zabbix
|
||||||
|
|
||||||
|
# Create log file with proper permissions if it doesn't exist
|
||||||
|
if [ ! -f /var/log/zabbix/php.log ]; then
|
||||||
|
touch /var/log/zabbix/php.log
|
||||||
|
chown www-data:zabbix /var/log/zabbix/php.log
|
||||||
|
chmod 644 /var/log/zabbix/php.log
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Set permissions on sudoers file
|
||||||
|
chmod 440 /etc/sudoers.d/zabbix-php-user
|
||||||
|
|
||||||
|
# Restart services to apply configuration
|
||||||
|
systemctl reload php8.3-fpm.service || true
|
||||||
|
systemctl restart zabbix-agent.service || true
|
||||||
|
|
||||||
|
echo "Zabbix PHP monitoring has been installed successfully."
|
||||||
|
echo "Log file: /var/log/zabbix/php.log"
|
||||||
|
echo "Template file: /usr/share/doc/zabbix-php-monitoring/zabbix_template_php_exceptions.xml"
|
||||||
|
echo ""
|
||||||
|
echo "To complete setup:"
|
||||||
|
echo "1. Import the template into your Zabbix server"
|
||||||
|
echo "2. Assign the template to your hosts"
|
||||||
|
echo "3. Configure zabbix_agentd.conf with appropriate log monitoring"
|
||||||
|
|
||||||
|
exit 0
|
||||||
15
src/DEBIAN/prerm
Executable file
15
src/DEBIAN/prerm
Executable file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Pre-removal script for zabbix-php-monitoring
|
||||||
|
|
||||||
|
echo "Removing zabbix-php-monitoring..."
|
||||||
|
|
||||||
|
# Restart PHP-FPM to clear the auto_prepend_file configuration
|
||||||
|
systemctl reload php8.3-fpm.service || true
|
||||||
|
|
||||||
|
# Note: We don't remove the log files as they may contain important data
|
||||||
|
echo "Log files in /var/log/zabbix/ have been preserved"
|
||||||
|
echo "Configuration files will be removed"
|
||||||
|
|
||||||
|
exit 0
|
||||||
16
src/etc/logrotate.d/zabbix-php
Normal file
16
src/etc/logrotate.d/zabbix-php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/var/log/zabbix/php.log {
|
||||||
|
daily
|
||||||
|
missingok
|
||||||
|
rotate 30
|
||||||
|
compress
|
||||||
|
delaycompress
|
||||||
|
notifempty
|
||||||
|
create 0644 www-data zabbix
|
||||||
|
copytruncate
|
||||||
|
postrotate
|
||||||
|
# Signal php-fpm to reopen log files (optional)
|
||||||
|
/bin/systemctl reload php8.3-fpm.service > /dev/null 2>&1 || true
|
||||||
|
# Signal zabbix-agent to reopen log files
|
||||||
|
/bin/systemctl reload zabbix-agent.service > /dev/null 2>&1 || true
|
||||||
|
endscript
|
||||||
|
}
|
||||||
11
src/etc/php/8.3/fpm/conf.d/99-zabbix-php.conf
Normal file
11
src/etc/php/8.3/fpm/conf.d/99-zabbix-php.conf
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
; Zabbix PHP Exception Monitoring Configuration
|
||||||
|
; This configuration enables automatic exception logging for Zabbix monitoring
|
||||||
|
|
||||||
|
; Auto-prepend the Zabbix exception handler to all PHP files
|
||||||
|
auto_prepend_file = /usr/lib/zabbix-php.php
|
||||||
|
|
||||||
|
; Ensure error reporting is enabled
|
||||||
|
log_errors = On
|
||||||
|
|
||||||
|
; Optional: Set custom error log for debugging (in addition to Zabbix logging)
|
||||||
|
; error_log = /var/log/php/error.log
|
||||||
18
src/etc/sudoers.d/zabbix-php-user
Normal file
18
src/etc/sudoers.d/zabbix-php-user
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Sudoers configuration for zabbix-php monitoring
|
||||||
|
# Allows user to manage zabbix-agent and monitor logs
|
||||||
|
|
||||||
|
# Allow user to restart zabbix-agent without password
|
||||||
|
user ALL=(root) NOPASSWD: /bin/systemctl restart zabbix-agent.service
|
||||||
|
user ALL=(root) NOPASSWD: /bin/systemctl reload zabbix-agent.service
|
||||||
|
user ALL=(root) NOPASSWD: /bin/systemctl status zabbix-agent.service
|
||||||
|
user ALL=(root) NOPASSWD: /bin/systemctl stop zabbix-agent.service
|
||||||
|
user ALL=(root) NOPASSWD: /bin/systemctl start zabbix-agent.service
|
||||||
|
|
||||||
|
# Allow user to view zabbix-agent logs via journalctl
|
||||||
|
user ALL=(root) NOPASSWD: /bin/journalctl -u zabbix-agent.service
|
||||||
|
user ALL=(root) NOPASSWD: /bin/journalctl -u zabbix-agent.service -f
|
||||||
|
user ALL=(root) NOPASSWD: /bin/journalctl -u zabbix-agent.service -n *
|
||||||
|
|
||||||
|
# Allow user to tail PHP exception log
|
||||||
|
user ALL=(root) NOPASSWD: /usr/bin/tail -f /var/log/zabbix/php.log
|
||||||
|
user ALL=(root) NOPASSWD: /usr/bin/tail -n * /var/log/zabbix/php.log
|
||||||
117
src/usr/lib/zabbix-php.php
Normal file
117
src/usr/lib/zabbix-php.php
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Zabbix PHP Exception Handler
|
||||||
|
* Captures and logs PHP exceptions for monitoring
|
||||||
|
*/
|
||||||
|
|
||||||
|
class ZabbixPHPExceptionHandler {
|
||||||
|
private static $logFile = '/var/log/zabbix/php.log';
|
||||||
|
private static $previousHandler = null;
|
||||||
|
private static $initialized = false;
|
||||||
|
|
||||||
|
public static function init() {
|
||||||
|
if (self::$initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store previous exception handler
|
||||||
|
self::$previousHandler = set_exception_handler([self::class, 'handleException']);
|
||||||
|
|
||||||
|
// Also handle fatal errors
|
||||||
|
register_shutdown_function([self::class, 'handleFatalError']);
|
||||||
|
|
||||||
|
self::$initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function handleException($exception) {
|
||||||
|
self::logException($exception);
|
||||||
|
|
||||||
|
// Call previous handler if exists
|
||||||
|
if (self::$previousHandler && is_callable(self::$previousHandler)) {
|
||||||
|
call_user_func(self::$previousHandler, $exception);
|
||||||
|
} else {
|
||||||
|
// Default behavior: display error and exit
|
||||||
|
if (php_sapi_name() !== 'cli') {
|
||||||
|
http_response_code(500);
|
||||||
|
if (ini_get('display_errors')) {
|
||||||
|
echo "Fatal error: Uncaught exception\n";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo "Fatal error: Uncaught exception: " . $exception->getMessage() . "\n";
|
||||||
|
}
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function handleFatalError() {
|
||||||
|
$error = error_get_last();
|
||||||
|
if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
|
||||||
|
$exception = new ErrorException(
|
||||||
|
$error['message'],
|
||||||
|
0,
|
||||||
|
$error['type'],
|
||||||
|
$error['file'],
|
||||||
|
$error['line']
|
||||||
|
);
|
||||||
|
self::logException($exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function logException($exception) {
|
||||||
|
try {
|
||||||
|
// Ensure log directory exists
|
||||||
|
$logDir = dirname(self::$logFile);
|
||||||
|
if (!is_dir($logDir)) {
|
||||||
|
mkdir($logDir, 0755, true);
|
||||||
|
// Set proper ownership for zabbix log directory
|
||||||
|
if (function_exists('chown')) {
|
||||||
|
@chown($logDir, 'zabbix');
|
||||||
|
@chgrp($logDir, 'zabbix');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get HTTP_HOST or set default
|
||||||
|
$httpHost = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] :
|
||||||
|
(isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'cli');
|
||||||
|
|
||||||
|
// Create unique identifier for deduplication
|
||||||
|
$uniqueId = md5($httpHost . '|' . $exception->getFile() . '|' . $exception->getLine() . '|' . $exception->getMessage());
|
||||||
|
|
||||||
|
// Prepare log entry
|
||||||
|
$logEntry = [
|
||||||
|
'timestamp' => date('Y-m-d H:i:s'),
|
||||||
|
'host' => $httpHost,
|
||||||
|
'file' => $exception->getFile(),
|
||||||
|
'line' => $exception->getLine(),
|
||||||
|
'message' => $exception->getMessage(),
|
||||||
|
'stacktrace' => $exception->getTraceAsString(),
|
||||||
|
'unique_id' => $uniqueId,
|
||||||
|
'request_uri' => isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '',
|
||||||
|
'user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '',
|
||||||
|
'remote_addr' => isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '',
|
||||||
|
'exception_class' => get_class($exception)
|
||||||
|
];
|
||||||
|
|
||||||
|
// Convert to JSON and write to log
|
||||||
|
$jsonEntry = json_encode($logEntry, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . "\n";
|
||||||
|
|
||||||
|
// Write to log file with proper locking
|
||||||
|
file_put_contents(self::$logFile, $jsonEntry, FILE_APPEND | LOCK_EX);
|
||||||
|
|
||||||
|
// Set proper permissions on log file
|
||||||
|
if (file_exists(self::$logFile)) {
|
||||||
|
@chmod(self::$logFile, 0644);
|
||||||
|
@chown(self::$logFile, 'www-data');
|
||||||
|
@chgrp(self::$logFile, 'zabbix');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
// Silently fail to avoid infinite loops
|
||||||
|
error_log("Zabbix PHP Exception Handler failed: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the exception handler
|
||||||
|
ZabbixPHPExceptionHandler::init();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<zabbix_export>
|
||||||
|
<version>6.4</version>
|
||||||
|
<date>2025-09-29T00:00:00Z</date>
|
||||||
|
<groups>
|
||||||
|
<group>
|
||||||
|
<uuid>7df96b18c230490a9a0a9e2307226338</uuid>
|
||||||
|
<name>Templates/Applications</name>
|
||||||
|
</group>
|
||||||
|
</groups>
|
||||||
|
<templates>
|
||||||
|
<template>
|
||||||
|
<uuid>c770cff7c4bf49e6b2b0e7a5f3d2c4a1</uuid>
|
||||||
|
<template>Template App PHP Exceptions</template>
|
||||||
|
<name>Template App PHP Exceptions</name>
|
||||||
|
<description>Template for monitoring PHP exceptions with deduplication</description>
|
||||||
|
<groups>
|
||||||
|
<group>
|
||||||
|
<name>Templates/Applications</name>
|
||||||
|
</group>
|
||||||
|
</groups>
|
||||||
|
<items>
|
||||||
|
<item>
|
||||||
|
<uuid>8df96b18c230490a9a0a9e2307226339</uuid>
|
||||||
|
<name>PHP Exception Monitor</name>
|
||||||
|
<type>ZABBIX_ACTIVE</type>
|
||||||
|
<key>log[/var/log/zabbix/php.log]</key>
|
||||||
|
<delay>1s</delay>
|
||||||
|
<history>7d</history>
|
||||||
|
<trends>0</trends>
|
||||||
|
<value_type>LOG</value_type>
|
||||||
|
<description>Monitor PHP exceptions from log file</description>
|
||||||
|
<preprocessing>
|
||||||
|
<step>
|
||||||
|
<type>REGEX</type>
|
||||||
|
<parameters>
|
||||||
|
<parameter>^(.*)$</parameter>
|
||||||
|
<parameter>\1</parameter>
|
||||||
|
</parameters>
|
||||||
|
</step>
|
||||||
|
<step>
|
||||||
|
<type>JAVASCRIPT</type>
|
||||||
|
<parameters>
|
||||||
|
<parameter>// Parse JSON log entry and format for display
|
||||||
|
try {
|
||||||
|
var entry = JSON.parse(value);
|
||||||
|
var formattedStack = entry.stacktrace.split('\n').map(function(line, index) {
|
||||||
|
return (index + 1) + ': ' + line.trim();
|
||||||
|
}).join('\n');
|
||||||
|
|
||||||
|
return JSON.stringify({
|
||||||
|
host: entry.host,
|
||||||
|
file: entry.file,
|
||||||
|
line: entry.line,
|
||||||
|
message: entry.message,
|
||||||
|
stacktrace: formattedStack,
|
||||||
|
timestamp: entry.timestamp,
|
||||||
|
unique_id: entry.unique_id
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
return value;
|
||||||
|
}</parameter>
|
||||||
|
</parameters>
|
||||||
|
</step>
|
||||||
|
</preprocessing>
|
||||||
|
</item>
|
||||||
|
</items>
|
||||||
|
<triggers>
|
||||||
|
<trigger>
|
||||||
|
<uuid>9df96b18c230490a9a0a9e2307226340</uuid>
|
||||||
|
<expression>logeventid(/Template App PHP Exceptions/log[/var/log/zabbix/php.log])</expression>
|
||||||
|
<name>PHP Exception: {ITEM.LASTVALUE}</name>
|
||||||
|
<opdata>{ITEM.LASTVALUE}</opdata>
|
||||||
|
<priority>WARNING</priority>
|
||||||
|
<description>A PHP exception has occurred</description>
|
||||||
|
<type>MULTIPLE</type>
|
||||||
|
<manual_close>YES</manual_close>
|
||||||
|
<correlation_mode>1</correlation_mode>
|
||||||
|
<correlation_tag>php_exception_{ITEM.LASTVALUE.regsub(".*\"unique_id\":\"([^\"]+)\".*", "\1")}</correlation_tag>
|
||||||
|
<dependencies/>
|
||||||
|
</trigger>
|
||||||
|
</triggers>
|
||||||
|
<macros>
|
||||||
|
<macro>
|
||||||
|
<macro>{$PHP.LOG.FILE}</macro>
|
||||||
|
<value>/var/log/zabbix/php.log</value>
|
||||||
|
<description>Path to PHP exception log file</description>
|
||||||
|
</macro>
|
||||||
|
</macros>
|
||||||
|
</template>
|
||||||
|
</templates>
|
||||||
|
<value_maps>
|
||||||
|
<value_map>
|
||||||
|
<uuid>adf96b18c230490a9a0a9e2307226341</uuid>
|
||||||
|
<name>PHP Exception Severity</name>
|
||||||
|
<mappings>
|
||||||
|
<mapping>
|
||||||
|
<value>1</value>
|
||||||
|
<newvalue>Fatal Error</newvalue>
|
||||||
|
</mapping>
|
||||||
|
<mapping>
|
||||||
|
<value>2</value>
|
||||||
|
<newvalue>Warning</newvalue>
|
||||||
|
</mapping>
|
||||||
|
<mapping>
|
||||||
|
<value>3</value>
|
||||||
|
<newvalue>Notice</newvalue>
|
||||||
|
</mapping>
|
||||||
|
</mappings>
|
||||||
|
</value_map>
|
||||||
|
</value_maps>
|
||||||
|
</zabbix_export>
|
||||||
68
zabbix_template_php_exceptions.yaml
Normal file
68
zabbix_template_php_exceptions.yaml
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
zabbix_export:
|
||||||
|
version: '6.4'
|
||||||
|
template_groups:
|
||||||
|
- uuid: 7df96b18c230490a9a0a9e2307226338
|
||||||
|
name: 'Templates/Applications'
|
||||||
|
templates:
|
||||||
|
- uuid: c770cff7c4bf49e6b2b0e7a5f3d2c4a1
|
||||||
|
template: 'PHP Exceptions Monitoring'
|
||||||
|
name: 'PHP Exceptions Monitoring'
|
||||||
|
description: 'Template for monitoring PHP exceptions with deduplication'
|
||||||
|
groups:
|
||||||
|
- name: 'Templates/Applications'
|
||||||
|
macros:
|
||||||
|
- macro: '{$PHP.LOG.FILE}'
|
||||||
|
value: '/var/log/zabbix/php.exceptions.log'
|
||||||
|
description: 'Path to PHP exception log file'
|
||||||
|
items:
|
||||||
|
- uuid: 8df96b18c230490a9a0a9e2307226339
|
||||||
|
name: 'PHP Exception Monitor'
|
||||||
|
type: ZABBIX_PASSIVE
|
||||||
|
key: 'logrt[/var/log/zabbix/php.exceptions.log]'
|
||||||
|
delay: 30s
|
||||||
|
history: 7d
|
||||||
|
trends: '0'
|
||||||
|
value_type: LOG
|
||||||
|
description: 'Monitor PHP exceptions from log file'
|
||||||
|
triggers:
|
||||||
|
- uuid: 9df96b18c230490a9a0a9e2307226340
|
||||||
|
expression: 'logeventid(/PHP Exceptions Monitoring/logrt[/var/log/zabbix/php.exceptions.log])'
|
||||||
|
name: 'PHP Exception occurred'
|
||||||
|
event_name: 'PHP Exception: {ITEM.LASTVALUE}'
|
||||||
|
priority: WARNING
|
||||||
|
description: |
|
||||||
|
A PHP exception has occurred.
|
||||||
|
Details: {ITEM.LASTVALUE}
|
||||||
|
manual_close: 'YES'
|
||||||
|
tags:
|
||||||
|
- tag: scope
|
||||||
|
value: application
|
||||||
|
- tag: component
|
||||||
|
value: php
|
||||||
|
preprocessing:
|
||||||
|
- type: REGEX
|
||||||
|
parameters:
|
||||||
|
- '^(.*)$'
|
||||||
|
- '\1'
|
||||||
|
- type: JAVASCRIPT
|
||||||
|
parameters:
|
||||||
|
- |
|
||||||
|
// Parse JSON log entry and format for display
|
||||||
|
try {
|
||||||
|
var entry = JSON.parse(value);
|
||||||
|
var formattedStack = entry.stacktrace.split('\n').map(function(line, index) {
|
||||||
|
return (index + 1) + ': ' + line.trim();
|
||||||
|
}).join('\n');
|
||||||
|
|
||||||
|
return JSON.stringify({
|
||||||
|
host: entry.host,
|
||||||
|
file: entry.file,
|
||||||
|
line: entry.line,
|
||||||
|
message: entry.message,
|
||||||
|
stacktrace: formattedStack,
|
||||||
|
timestamp: entry.timestamp,
|
||||||
|
unique_id: entry.unique_id
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user