Build production-grade custom Prometheus exporters in Python and Go to collect application-specific metrics. Learn exporter architecture, metric types, systemd deployment, and Prometheus integration for comprehensive application monitoring.
Prerequisites
- Root or sudo access
- Python 3.8+ installed
- Go 1.19+ installed
- Prometheus server running
- Basic understanding of systemd services
What this solves
Custom Prometheus exporters allow you to collect application-specific metrics that aren't available through standard exporters. This tutorial shows you how to build HTTP-based exporters in Python and Go, implement process and file-based metric collection, and deploy them as systemd services with proper Prometheus integration.
Understanding Prometheus exporter architecture
Prometheus exporters follow a pull-based model where Prometheus scrapes metrics from HTTP endpoints. Exporters expose metrics in a specific text format at /metrics endpoints, typically running on dedicated ports.
Metric types and formats
Prometheus supports four core metric types that serve different monitoring purposes:
| Type | Purpose | Example Use Case |
|---|---|---|
| Counter | Monotonically increasing values | HTTP requests, errors, processed jobs |
| Gauge | Values that can go up or down | Memory usage, active connections, queue size |
| Histogram | Distribution of values with buckets | Request duration, response sizes |
| Summary | Similar to histogram with quantiles | Response times with percentiles |
Step-by-step implementation
Install required dependencies
Install Python development tools and Go compiler for building custom exporters.
sudo apt update
sudo apt install -y python3 python3-pip python3-venv golang-go curl
Create dedicated user for exporters
Create a non-privileged user to run the exporters for security isolation.
sudo useradd --system --no-create-home --shell /bin/false prometheus-exporters
sudo mkdir -p /opt/prometheus-exporters
sudo chown prometheus-exporters:prometheus-exporters /opt/prometheus-exporters
Build Python-based HTTP exporter
Create a Python exporter that collects application metrics and exposes them via HTTP.
sudo mkdir -p /opt/prometheus-exporters/python-app
cd /opt/prometheus-exporters/python-app
python3 -m venv venv
source venv/bin/activate
pip install prometheus_client psutil requests
Create Python exporter application
Build a comprehensive exporter that monitors application processes, file metrics, and custom business logic.
#!/usr/bin/env python3
import time
import psutil
import os
import json
from prometheus_client import start_http_server, Gauge, Counter, Histogram, Info
from prometheus_client.core import CollectorRegistry
from threading import Thread
import logging
Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
class ApplicationExporter:
def __init__(self, config_file='/etc/prometheus-exporters/app-config.json'):
self.config = self.load_config(config_file)
self.registry = CollectorRegistry()
# Define metrics
self.app_info = Info('app_version', 'Application version info', registry=self.registry)
self.process_count = Gauge('app_processes_total', 'Number of application processes', ['name'], registry=self.registry)
self.file_size_bytes = Gauge('app_file_size_bytes', 'Size of monitored files', ['path'], registry=self.registry)
self.file_age_seconds = Gauge('app_file_age_seconds', 'Age of monitored files', ['path'], registry=self.registry)
self.request_count = Counter('app_requests_total', 'Total application requests', ['method', 'endpoint'], registry=self.registry)
self.response_time = Histogram('app_response_duration_seconds', 'Response time histogram', ['endpoint'], registry=self.registry)
self.memory_usage_bytes = Gauge('app_memory_usage_bytes', 'Memory usage by process', ['process'], registry=self.registry)
self.cpu_usage_percent = Gauge('app_cpu_usage_percent', 'CPU usage by process', ['process'], registry=self.registry)
# Set application info
self.app_info.info({
'version': self.config.get('version', '1.0.0'),
'environment': self.config.get('environment', 'production')
})
def load_config(self, config_file):
try:
with open(config_file, 'r') as f:
return json.load(f)
except FileNotFoundError:
logger.warning(f"Config file {config_file} not found, using defaults")
return {
'monitored_processes': ['nginx', 'postgres', 'redis-server'],
'monitored_files': ['/var/log/app.log', '/var/lib/app/data.db'],
'version': '1.0.0',
'environment': 'production'
}
def collect_process_metrics(self):
"""Collect metrics for monitored processes"""
for process_name in self.config.get('monitored_processes', []):
count = 0
total_memory = 0
total_cpu = 0
for proc in psutil.process_iter(['pid', 'name', 'memory_info', 'cpu_percent']):
try:
if proc.info['name'] == process_name:
count += 1
total_memory += proc.info['memory_info'].rss
total_cpu += proc.info['cpu_percent']
except (psutil.NoSuchProcess, psutil.AccessDenied):
continue
self.process_count.labels(name=process_name).set(count)
if count > 0:
self.memory_usage_bytes.labels(process=process_name).set(total_memory)
self.cpu_usage_percent.labels(process=process_name).set(total_cpu)
def collect_file_metrics(self):
"""Collect metrics for monitored files"""
for file_path in self.config.get('monitored_files', []):
try:
stat = os.stat(file_path)
self.file_size_bytes.labels(path=file_path).set(stat.st_size)
self.file_age_seconds.labels(path=file_path).set(time.time() - stat.st_mtime)
except FileNotFoundError:
logger.warning(f"File {file_path} not found")
self.file_size_bytes.labels(path=file_path).set(0)
self.file_age_seconds.labels(path=file_path).set(-1)
def simulate_request_metrics(self):
"""Simulate request metrics (replace with actual application integration)"""
endpoints = ['/api/users', '/api/orders', '/api/health']
methods = ['GET', 'POST', 'PUT']
import random
endpoint = random.choice(endpoints)
method = random.choice(methods)
response_time = random.uniform(0.01, 2.0)
self.request_count.labels(method=method, endpoint=endpoint).inc()
self.response_time.labels(endpoint=endpoint).observe(response_time)
def collect_metrics(self):
"""Main metric collection method"""
while True:
try:
self.collect_process_metrics()
self.collect_file_metrics()
self.simulate_request_metrics()
time.sleep(10) # Collect metrics every 10 seconds
except Exception as e:
logger.error(f"Error collecting metrics: {e}")
time.sleep(10)
def start(self, port=8000):
"""Start the HTTP server and metric collection"""
logger.info(f"Starting application exporter on port {port}")
start_http_server(port, registry=self.registry)
# Start metric collection in background thread
collection_thread = Thread(target=self.collect_metrics)
collection_thread.daemon = True
collection_thread.start()
logger.info("Application exporter started successfully")
return collection_thread
if __name__ == '__main__':
exporter = ApplicationExporter()
collection_thread = exporter.start(port=8000)
try:
# Keep the main thread alive
while True:
time.sleep(1)
except KeyboardInterrupt:
logger.info("Shutting down application exporter")
Create exporter configuration
Configure which processes and files to monitor for metrics collection.
sudo mkdir -p /etc/prometheus-exporters
{
"version": "2.1.0",
"environment": "production",
"monitored_processes": [
"nginx",
"postgres",
"redis-server",
"gunicorn",
"celery"
],
"monitored_files": [
"/var/log/nginx/access.log",
"/var/log/nginx/error.log",
"/var/lib/postgresql/data/postgresql.log",
"/var/log/app/application.log",
"/etc/nginx/nginx.conf"
]
}
Build Go-based exporter
Create a high-performance Go exporter for collecting system and application metrics.
mkdir -p /opt/prometheus-exporters/go-system
cd /opt/prometheus-exporters/go-system
go mod init system-exporter
go get github.com/prometheus/client_golang/prometheus
go get github.com/prometheus/client_golang/prometheus/promhttp
Create Go exporter application
Implement a Go exporter that provides efficient system metric collection.
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"strconv"
"strings"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
type Config struct {
Port int json:"port"
MetricsPath string json:"metrics_path"
ScrapeInterval int json:"scrape_interval"
MonitoredPorts []int json:"monitored_ports"
LogFiles []string json:"log_files"
}
type SystemExporter struct {
config *Config
systemLoad1 prometheus.Gauge
systemLoad5 prometheus.Gauge
systemLoad15 prometheus.Gauge
memoryTotal prometheus.Gauge
memoryAvailable prometheus.Gauge
memoryUsed prometheus.Gauge
diskUsedBytes *prometheus.GaugeVec
diskTotalBytes *prometheus.GaugeVec
networkReceiveBytes *prometheus.CounterVec
networkTransmitBytes *prometheus.CounterVec
portStatus *prometheus.GaugeVec
logFileSizeBytes *prometheus.GaugeVec
logFileModTime *prometheus.GaugeVec
uptime prometheus.Gauge
}
func NewSystemExporter(configPath string) (*SystemExporter, error) {
config, err := loadConfig(configPath)
if err != nil {
return nil, fmt.Errorf("failed to load config: %w", err)
}
exporter := &SystemExporter{
config: config,
systemLoad1: prometheus.NewGauge(prometheus.GaugeOpts{
Name: "system_load_1m",
Help: "System load average over 1 minute",
}),
systemLoad5: prometheus.NewGauge(prometheus.GaugeOpts{
Name: "system_load_5m",
Help: "System load average over 5 minutes",
}),
systemLoad15: prometheus.NewGauge(prometheus.GaugeOpts{
Name: "system_load_15m",
Help: "System load average over 15 minutes",
}),
memoryTotal: prometheus.NewGauge(prometheus.GaugeOpts{
Name: "system_memory_total_bytes",
Help: "Total system memory in bytes",
}),
memoryAvailable: prometheus.NewGauge(prometheus.GaugeOpts{
Name: "system_memory_available_bytes",
Help: "Available system memory in bytes",
}),
memoryUsed: prometheus.NewGauge(prometheus.GaugeOpts{
Name: "system_memory_used_bytes",
Help: "Used system memory in bytes",
}),
diskUsedBytes: prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "system_disk_used_bytes",
Help: "Used disk space in bytes",
},
[]string{"device", "mountpoint"},
),
diskTotalBytes: prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "system_disk_total_bytes",
Help: "Total disk space in bytes",
},
[]string{"device", "mountpoint"},
),
networkReceiveBytes: prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "system_network_receive_bytes_total",
Help: "Total bytes received on network interfaces",
},
[]string{"interface"},
),
networkTransmitBytes: prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "system_network_transmit_bytes_total",
Help: "Total bytes transmitted on network interfaces",
},
[]string{"interface"},
),
portStatus: prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "system_port_status",
Help: "Status of monitored ports (1=listening, 0=not listening)",
},
[]string{"port"},
),
logFileSizeBytes: prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "system_log_file_size_bytes",
Help: "Size of log files in bytes",
},
[]string{"file"},
),
logFileModTime: prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "system_log_file_modification_time",
Help: "Last modification time of log files",
},
[]string{"file"},
),
uptime: prometheus.NewGauge(prometheus.GaugeOpts{
Name: "system_uptime_seconds",
Help: "System uptime in seconds",
}),
}
// Register metrics
prometheus.MustRegister(
exporter.systemLoad1,
exporter.systemLoad5,
exporter.systemLoad15,
exporter.memoryTotal,
exporter.memoryAvailable,
exporter.memoryUsed,
exporter.diskUsedBytes,
exporter.diskTotalBytes,
exporter.networkReceiveBytes,
exporter.networkTransmitBytes,
exporter.portStatus,
exporter.logFileSizeBytes,
exporter.logFileModTime,
exporter.uptime,
)
return exporter, nil
}
func loadConfig(configPath string) (*Config, error) {
config := &Config{
Port: 8001,
MetricsPath: "/metrics",
ScrapeInterval: 15,
MonitoredPorts: []int{22, 80, 443, 5432, 6379},
LogFiles: []string{"/var/log/syslog", "/var/log/auth.log"},
}
if _, err := os.Stat(configPath); os.IsNotExist(err) {
log.Printf("Config file %s not found, using defaults", configPath)
return config, nil
}
data, err := ioutil.ReadFile(configPath)
if err != nil {
return nil, err
}
err = json.Unmarshal(data, config)
if err != nil {
return nil, err
}
return config, nil
}
func (e *SystemExporter) collectLoadAverage() {
data, err := ioutil.ReadFile("/proc/loadavg")
if err != nil {
log.Printf("Error reading load average: %v", err)
return
}
fields := strings.Fields(string(data))
if len(fields) >= 3 {
if load1, err := strconv.ParseFloat(fields[0], 64); err == nil {
e.systemLoad1.Set(load1)
}
if load5, err := strconv.ParseFloat(fields[1], 64); err == nil {
e.systemLoad5.Set(load5)
}
if load15, err := strconv.ParseFloat(fields[2], 64); err == nil {
e.systemLoad15.Set(load15)
}
}
}
func (e *SystemExporter) collectMemoryStats() {
data, err := ioutil.ReadFile("/proc/meminfo")
if err != nil {
log.Printf("Error reading memory info: %v", err)
return
}
lines := strings.Split(string(data), "\n")
for _, line := range lines {
fields := strings.Fields(line)
if len(fields) < 2 {
continue
}
value, err := strconv.ParseFloat(fields[1], 64)
if err != nil {
continue
}
// Convert KB to bytes
value *= 1024
switch fields[0] {
case "MemTotal:":
e.memoryTotal.Set(value)
case "MemAvailable:":
e.memoryAvailable.Set(value)
}
}
// Calculate used memory
total := e.memoryTotal
available := e.memoryAvailable
if total != nil && available != nil {
// This is a simplified calculation
// In production, you'd want more accurate memory calculations
}
}
func (e *SystemExporter) collectPortStatus() {
for _, port := range e.config.MonitoredPorts {
address := fmt.Sprintf("127.0.0.1:%d", port)
conn, err := net.DialTimeout("tcp", address, 1*time.Second)
if err != nil {
e.portStatus.WithLabelValues(strconv.Itoa(port)).Set(0)
} else {
conn.Close()
e.portStatus.WithLabelValues(strconv.Itoa(port)).Set(1)
}
}
}
func (e *SystemExporter) collectLogFileStats() {
for _, logFile := range e.config.LogFiles {
stat, err := os.Stat(logFile)
if err != nil {
log.Printf("Error getting stats for %s: %v", logFile, err)
e.logFileSizeBytes.WithLabelValues(logFile).Set(0)
e.logFileModTime.WithLabelValues(logFile).Set(0)
continue
}
e.logFileSizeBytes.WithLabelValues(logFile).Set(float64(stat.Size()))
e.logFileModTime.WithLabelValues(logFile).Set(float64(stat.ModTime().Unix()))
}
}
func (e *SystemExporter) collectUptime() {
data, err := ioutil.ReadFile("/proc/uptime")
if err != nil {
log.Printf("Error reading uptime: %v", err)
return
}
fields := strings.Fields(string(data))
if len(fields) >= 1 {
if uptime, err := strconv.ParseFloat(fields[0], 64); err == nil {
e.uptime.Set(uptime)
}
}
}
func (e *SystemExporter) collectMetrics() {
for {
e.collectLoadAverage()
e.collectMemoryStats()
e.collectPortStatus()
e.collectLogFileStats()
e.collectUptime()
time.Sleep(time.Duration(e.config.ScrapeInterval) * time.Second)
}
}
func (e *SystemExporter) Start() {
go e.collectMetrics()
http.Handle(e.config.MetricsPath, promhttp.Handler())
log.Printf("System exporter starting on port %d", e.config.Port)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", e.config.Port), nil))
}
func main() {
exporter, err := NewSystemExporter("/etc/prometheus-exporters/system-config.json")
if err != nil {
log.Fatalf("Failed to create system exporter: %v", err)
}
exporter.Start()
}
Create Go exporter configuration
Configure the Go exporter with specific ports and files to monitor.
{
"port": 8001,
"metrics_path": "/metrics",
"scrape_interval": 15,
"monitored_ports": [
22,
80,
443,
5432,
6379,
9090,
3000
],
"log_files": [
"/var/log/syslog",
"/var/log/auth.log",
"/var/log/nginx/access.log",
"/var/log/nginx/error.log"
]
}
Build the Go exporter
Compile the Go exporter into an optimized binary for production deployment.
cd /opt/prometheus-exporters/go-system
go build -o system-exporter -ldflags "-w -s" main.go
Set proper permissions
Configure ownership and permissions for the exporter files and directories.
sudo chown -R prometheus-exporters:prometheus-exporters /opt/prometheus-exporters
sudo chown -R prometheus-exporters:prometheus-exporters /etc/prometheus-exporters
sudo chmod +x /opt/prometheus-exporters/python-app/app_exporter.py
sudo chmod +x /opt/prometheus-exporters/go-system/system-exporter
Create systemd service for Python exporter
Deploy the Python exporter as a systemd service for automatic startup and process management.
[Unit]
Description=Prometheus Application Exporter
After=network.target
Wants=network.target
[Service]
Type=simple
User=prometheus-exporters
Group=prometheus-exporters
WorkingDirectory=/opt/prometheus-exporters/python-app
Environment=PYTHONPATH=/opt/prometheus-exporters/python-app
ExecStart=/opt/prometheus-exporters/python-app/venv/bin/python /opt/prometheus-exporters/python-app/app_exporter.py
Restart=always
RestartSec=10
KillMode=mixed
TimeoutStopSec=30
SyslogIdentifier=prometheus-app-exporter
Security settings
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/var/log
CapabilityBoundingSet=
AmbientCapabilities=
SystemCallFilter=@system-service
SystemCallFilter=~@privileged
[Install]
WantedBy=multi-user.target
Create systemd service for Go exporter
Deploy the Go exporter as a systemd service with security hardening.
[Unit]
Description=Prometheus System Exporter
After=network.target
Wants=network.target
[Service]
Type=simple
User=prometheus-exporters
Group=prometheus-exporters
WorkingDirectory=/opt/prometheus-exporters/go-system
ExecStart=/opt/prometheus-exporters/go-system/system-exporter
Restart=always
RestartSec=10
KillMode=mixed
TimeoutStopSec=30
SyslogIdentifier=prometheus-system-exporter
Security settings
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadOnlyPaths=/proc
ReadOnlyPaths=/sys
ReadOnlyPaths=/var/log
CapabilityBoundingSet=
AmbientCapabilities=
SystemCallFilter=@system-service
SystemCallFilter=~@privileged
[Install]
WantedBy=multi-user.target
Enable and start the exporters
Start both exporters and enable them for automatic startup on boot.
sudo systemctl daemon-reload
sudo systemctl enable --now prometheus-app-exporter
sudo systemctl enable --now prometheus-system-exporter
Configure Prometheus integration
Add the custom exporters to your Prometheus configuration for metric scraping.
# Add these scrape configs to your existing prometheus.yml
scrape_configs:
- job_name: 'custom-app-exporter'
static_configs:
- targets: ['localhost:8000']
scrape_interval: 15s
scrape_timeout: 10s
metrics_path: /metrics
scheme: http
- job_name: 'custom-system-exporter'
static_configs:
- targets: ['localhost:8001']
scrape_interval: 15s
scrape_timeout: 10s
metrics_path: /metrics
scheme: http
Restart Prometheus to apply configuration
Reload Prometheus configuration to start collecting metrics from your custom exporters.
sudo systemctl restart prometheus
sudo systemctl status prometheus
Verify your setup
Test that your custom exporters are working correctly and Prometheus can scrape metrics.
sudo systemctl status prometheus-app-exporter
sudo systemctl status prometheus-system-exporter
curl -s http://localhost:8000/metrics | head -20
curl -s http://localhost:8001/metrics | head -20
Check that Prometheus is successfully scraping your exporters:
curl -s http://localhost:9090/api/v1/targets | jq '.data.activeTargets[] | select(.labels.job | startswith("custom")) | {job: .labels.job, health: .health, lastScrape: .lastScrape}'
Query some metrics to ensure they're being collected:
curl -s 'http://localhost:9090/api/v1/query?query=app_processes_total' | jq '.data.result'
curl -s 'http://localhost:9090/api/v1/query?query=system_load_1m' | jq '.data.result'
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Exporter won't start | Permission denied on files | sudo chown -R prometheus-exporters:prometheus-exporters /opt/prometheus-exporters |
| Metrics endpoint returns 404 | Wrong port or path in Prometheus config | Check exporter logs: sudo journalctl -u prometheus-app-exporter -f |
| Python exporter crashes on startup | Missing dependencies | Reinstall in virtual environment: pip install prometheus_client psutil |
| Go exporter can't read system files | Insufficient permissions | Add ReadOnlyPaths=/proc to systemd service file |
| Prometheus can't scrape exporters | Firewall blocking connections | Allow ports: sudo ufw allow 8000 and sudo ufw allow 8001 |
| High memory usage in Python exporter | Memory leak in metric collection | Add memory limits to systemd: MemoryHigh=100M |
Next steps
- Configure Prometheus long-term storage with Thanos for unlimited data retention
- Configure Gunicorn performance monitoring with Prometheus metrics and Grafana dashboards
- Monitor Consul with Prometheus and Grafana for service discovery observability
- Set up Prometheus AlertManager with email and Slack notifications
- Implement Prometheus federation for multi-cluster monitoring
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Prometheus Custom Exporters Installation Script
# Installs Python and Go-based Prometheus exporters with systemd services
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Variables
EXPORTER_USER="prometheus-exporters"
INSTALL_DIR="/opt/prometheus-exporters"
CONFIG_DIR="/etc/prometheus-exporters"
PYTHON_PORT=${1:-9090}
GO_PORT=${2:-9091}
usage() {
echo "Usage: $0 [python_port] [go_port]"
echo " python_port: Port for Python exporter (default: 9090)"
echo " go_port: Port for Go exporter (default: 9091)"
exit 1
}
if [[ $# -gt 2 ]]; then
usage
fi
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}This script must be run as root${NC}"
exit 1
fi
# Cleanup function
cleanup() {
echo -e "${RED}Installation failed. Cleaning up...${NC}"
systemctl stop prometheus-python-exporter 2>/dev/null || true
systemctl stop prometheus-go-exporter 2>/dev/null || true
rm -rf "$INSTALL_DIR" "$CONFIG_DIR"
userdel "$EXPORTER_USER" 2>/dev/null || true
}
trap cleanup ERR
# Detect OS
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update"
PKG_INSTALL="apt install -y"
PYTHON_PKG="python3 python3-pip python3-venv"
GO_PKG="golang-go"
DEV_TOOLS="build-essential"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf check-update || true"
PKG_INSTALL="dnf install -y"
PYTHON_PKG="python3 python3-pip"
GO_PKG="golang"
DEV_TOOLS="gcc gcc-c++ make"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum check-update || true"
PKG_INSTALL="yum install -y"
PYTHON_PKG="python3 python3-pip"
GO_PKG="golang"
DEV_TOOLS="gcc gcc-c++ make"
;;
*)
echo -e "${RED}Unsupported distro: $ID${NC}"
exit 1
;;
esac
fi
echo -e "${GREEN}[1/8] Updating package cache...${NC}"
$PKG_UPDATE
echo -e "${GREEN}[2/8] Installing dependencies...${NC}"
$PKG_INSTALL $PYTHON_PKG $GO_PKG $DEV_TOOLS curl
echo -e "${GREEN}[3/8] Creating system user and directories...${NC}"
useradd --system --no-create-home --shell /bin/false "$EXPORTER_USER" || true
mkdir -p "$INSTALL_DIR" "$CONFIG_DIR"
chown "$EXPORTER_USER:$EXPORTER_USER" "$INSTALL_DIR" "$CONFIG_DIR"
echo -e "${GREEN}[4/8] Setting up Python exporter...${NC}"
mkdir -p "$INSTALL_DIR/python-app"
cd "$INSTALL_DIR/python-app"
python3 -m venv venv
source venv/bin/activate
pip install prometheus_client psutil requests
cat > app_exporter.py << 'EOF'
#!/usr/bin/env python3
import time
import psutil
import os
import json
from prometheus_client import start_http_server, Gauge, Counter, Histogram
from prometheus_client.core import CollectorRegistry
import logging
import sys
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class ApplicationExporter:
def __init__(self, port=9090):
self.port = port
self.registry = CollectorRegistry()
# Define metrics
self.process_count = Gauge('app_processes_total', 'Number of processes', ['name'], registry=self.registry)
self.memory_usage = Gauge('app_memory_usage_bytes', 'Memory usage', ['process'], registry=self.registry)
self.cpu_usage = Gauge('app_cpu_usage_percent', 'CPU usage', ['process'], registry=self.registry)
self.disk_usage = Gauge('app_disk_usage_percent', 'Disk usage', ['mount'], registry=self.registry)
def collect_metrics(self):
# Process metrics
processes = {}
for proc in psutil.process_iter(['name', 'memory_info', 'cpu_percent']):
try:
name = proc.info['name']
processes[name] = processes.get(name, 0) + 1
self.memory_usage.labels(process=name).set(proc.info['memory_info'].rss)
self.cpu_usage.labels(process=name).set(proc.info['cpu_percent'])
except:
continue
for name, count in processes.items():
self.process_count.labels(name=name).set(count)
# Disk usage
for disk in psutil.disk_partitions():
try:
usage = psutil.disk_usage(disk.mountpoint)
self.disk_usage.labels(mount=disk.mountpoint).set(usage.percent)
except:
continue
def run(self):
start_http_server(self.port, registry=self.registry)
logger.info(f"Python exporter started on port {self.port}")
while True:
self.collect_metrics()
time.sleep(15)
if __name__ == '__main__':
port = int(sys.argv[1]) if len(sys.argv) > 1 else 9090
exporter = ApplicationExporter(port)
exporter.run()
EOF
chmod 755 app_exporter.py
chown -R "$EXPORTER_USER:$EXPORTER_USER" "$INSTALL_DIR/python-app"
echo -e "${GREEN}[5/8] Setting up Go exporter...${NC}"
mkdir -p "$INSTALL_DIR/go-app"
cd "$INSTALL_DIR/go-app"
cat > main.go << 'EOF'
package main
import (
"fmt"
"log"
"net/http"
"os"
"strconv"
"time"
"runtime"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
goroutines = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "app_goroutines_total",
Help: "Number of goroutines",
},
[]string{"type"},
)
memStats = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "app_memory_stats_bytes",
Help: "Memory statistics",
},
[]string{"type"},
)
)
func init() {
prometheus.MustRegister(goroutines)
prometheus.MustRegister(memStats)
}
func collectMetrics() {
goroutines.With(prometheus.Labels{"type": "runtime"}).Set(float64(runtime.NumGoroutine()))
var m runtime.MemStats
runtime.ReadMemStats(&m)
memStats.With(prometheus.Labels{"type": "alloc"}).Set(float64(m.Alloc))
memStats.With(prometheus.Labels{"type": "sys"}).Set(float64(m.Sys))
memStats.With(prometheus.Labels{"type": "heap"}).Set(float64(m.HeapAlloc))
}
func main() {
port := "9091"
if len(os.Args) > 1 {
port = os.Args[1]
}
go func() {
for {
collectMetrics()
time.Sleep(15 * time.Second)
}
}()
http.Handle("/metrics", promhttp.Handler())
log.Printf("Go exporter started on port %s", port)
log.Fatal(http.ListenAndServe(":"+port, nil))
}
EOF
go mod init prometheus-go-exporter
go mod tidy
go get github.com/prometheus/client_golang/prometheus
go get github.com/prometheus/client_golang/prometheus/promhttp
go build -o prometheus-go-exporter main.go
chown -R "$EXPORTER_USER:$EXPORTER_USER" "$INSTALL_DIR/go-app"
echo -e "${GREEN}[6/8] Creating configuration files...${NC}"
cat > "$CONFIG_DIR/app-config.json" << EOF
{
"version": "1.0.0",
"environment": "production",
"monitored_processes": ["nginx", "apache2", "httpd"],
"monitored_files": ["/var/log/messages", "/var/log/syslog"]
}
EOF
chown "$EXPORTER_USER:$EXPORTER_USER" "$CONFIG_DIR/app-config.json"
echo -e "${GREEN}[7/8] Creating systemd services...${NC}"
cat > /etc/systemd/system/prometheus-python-exporter.service << EOF
[Unit]
Description=Prometheus Python Application Exporter
After=network.target
Wants=network-online.target
[Service]
Type=simple
User=$EXPORTER_USER
Group=$EXPORTER_USER
ExecStart=$INSTALL_DIR/python-app/venv/bin/python $INSTALL_DIR/python-app/app_exporter.py $PYTHON_PORT
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF
cat > /etc/systemd/system/prometheus-go-exporter.service << EOF
[Unit]
Description=Prometheus Go Application Exporter
After=network.target
Wants=network-online.target
[Service]
Type=simple
User=$EXPORTER_USER
Group=$EXPORTER_USER
ExecStart=$INSTALL_DIR/go-app/prometheus-go-exporter $GO_PORT
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable prometheus-python-exporter prometheus-go-exporter
systemctl start prometheus-python-exporter prometheus-go-exporter
echo -e "${GREEN}[8/8] Configuring firewall...${NC}"
if command -v ufw >/dev/null 2>&1; then
ufw allow "$PYTHON_PORT/tcp"
ufw allow "$GO_PORT/tcp"
elif command -v firewall-cmd >/dev/null 2>&1; then
firewall-cmd --permanent --add-port="$PYTHON_PORT/tcp"
firewall-cmd --permanent --add-port="$GO_PORT/tcp"
firewall-cmd --reload
fi
echo -e "${GREEN}Verifying installation...${NC}"
sleep 5
if systemctl is-active --quiet prometheus-python-exporter; then
echo -e "${GREEN}✓ Python exporter service is running${NC}"
else
echo -e "${RED}✗ Python exporter service failed${NC}"
fi
if systemctl is-active --quiet prometheus-go-exporter; then
echo -e "${GREEN}✓ Go exporter service is running${NC}"
else
echo -e "${RED}✗ Go exporter service failed${NC}"
fi
if curl -s "http://localhost:$PYTHON_PORT/metrics" >/dev/null; then
echo -e "${GREEN}✓ Python exporter HTTP endpoint responding${NC}"
else
echo -e "${YELLOW}⚠ Python exporter HTTP endpoint not responding${NC}"
fi
if curl -s "http://localhost:$GO_PORT/metrics" >/dev/null; then
echo -e "${GREEN}✓ Go exporter HTTP endpoint responding${NC}"
else
echo -e "${YELLOW}⚠ Go exporter HTTP endpoint not responding${NC}"
fi
echo -e "${GREEN}Installation completed successfully!${NC}"
echo -e "Python exporter: http://localhost:$PYTHON_PORT/metrics"
echo -e "Go exporter: http://localhost:$GO_PORT/metrics"
echo -e "Service status: systemctl status prometheus-python-exporter prometheus-go-exporter"
echo -e "Logs: journalctl -u prometheus-python-exporter -u prometheus-go-exporter"
Review the script before running. Execute with: bash install.sh