运行代码  

1.nginx.conf  首先加入获取请求耗时的模块  
  log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                  '$status $body_bytes_sent "$http_referer" '
                  '"$http_user_agent" "$http_x_forwarded_for" '
                  '$request_time';
  access_log /home/nginxWebUI/log/access.log  main;


2.网页打开http://localhost:9099/actuator/prometheus  


3.可以看到    nginx_request_latency_histogram_seconds_bucket{application="nginx-monitor",le="0.001",} 0.0      



4.然后再prometheus.yaml 中配置    
  - job_name: 'nginx-monitor'
    metrics_path: '/actuator/prometheus'
    static_configs:
      - targets: ['10.211.55.17:9099']
    scrape_interval: 5s   


5.然后再grafana 中配置p99指标即可  
histogram_quantile(0.99, sum(rate(http_server_requests_seconds_bucket[5m])) by (le))

 想获取nginx请求耗时的p99指标:nginx_request_latency_histogram_seconds_bucket

1.nginx-prometheus-exporter和nginx-vts 两种是无法暴露这个指标的

2.openresty  需要自定的lua脚本、各种的配置和nginx-plus  都会改变原先的nginx结构

在不改变原配置的情况下  使用java+prometheus  自定义nginx的p99指标且暴露接口,并用grafana展示p99耗时数据。

      <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>io.micrometer</groupId>
            <artifactId>micrometer-registry-prometheus</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
package com.NinxMonitor.Collector;

import com.NinxMonitor.pojo.NginxLogEntry;
import io.micrometer.core.instrument.DistributionSummary;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.file.*;
import java.time.Duration;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Slf4j
@Component
public class NginxMetricsCollector {

    private final MeterRegistry meterRegistry;
    private final DistributionSummary requestLatency;
    private final Map<Integer, Timer> statusCodeTimers = new ConcurrentHashMap<>();
    private long lastPosition = 0;
    //private static final String LOG_PATH = "/Users/zero/Desktop/access.log";
    //private static final String LOG_PATH = "${log.path}";

    private final Timer requestLatencyHistogram;


     @Value("${log.path}")
     String  LOG_PATH;

    private static final Pattern LOG_PATTERN = Pattern.compile(
            "^(\\S+) - \\S+ \\[(.*?)\\] \"(.*?)\" (\\d+) (\\d+) \"(.*?)\" \"(.*?)\" \"(.*?)\" ([0-9.]+)$"
    );

    /**
     *  将指标注册到普罗米修斯
     * @param meterRegistry
     * DistributionSummary  计算p指标的值  DistributionSummary 会将记录的延迟数据存储在分桶(Buckets)中,分桶的粒度由 Micrometer 内部配置决定。
     */
    public NginxMetricsCollector(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;

        // 请求延迟指标
        this.requestLatency = DistributionSummary.builder("nginx_request_latency_seconds")
                .description("Nginx request latency in seconds")
                .publishPercentiles(0.50, 0.90, 0.95, 0.99)
                .scale(1)
                .register(meterRegistry);

        this.requestLatencyHistogram = Timer.builder("nginx_request_latency_histogram")
                .description("Nginx request latency in seconds (histogram)")
                .publishPercentileHistogram()
                .serviceLevelObjectives(
                        Duration.ofMillis(10),    // 10ms
                        Duration.ofMillis(50),    // 50ms
                        Duration.ofMillis(100),   // 100ms
                        Duration.ofMillis(200),   // 200ms
                        Duration.ofMillis(500),   // 500ms
                        Duration.ofSeconds(1),    // 1s
                        Duration.ofSeconds(2),    // 2s
                        Duration.ofSeconds(5)     // 5s
                ).register(meterRegistry);
    }


    /**
     * 将两种指标的record方式合一   记录延迟指标
     * @param seconds
     */
    public  void recordLatency(double seconds) {
        requestLatency.record(seconds);
        requestLatencyHistogram.record(Duration.ofNanos((long)(seconds * 1_000_000_000)));  //纳秒数的 Duration 对象
    }


    /**
     * 定时任务执行获取日志信息
     */
    @Scheduled(fixedRate = 5000)   //五秒执行一次
    public void collectMetrics() {
        log.info("加载时间"+new Date(System.currentTimeMillis()));
        try {
            Path logPath = Paths.get(LOG_PATH);
            log.info("日志加载路径:"+LOG_PATH);
            if (!Files.exists(logPath)) {
                log.warn("Nginx log file not found: {}", LOG_PATH);
                return;
            }

            long fileSize = Files.size(logPath);
            if (fileSize < lastPosition) {
                // 日志文件被轮转,重置位置
                lastPosition = 0;
            }

            if (fileSize > lastPosition) {
                try (RandomAccessFile file = new RandomAccessFile(LOG_PATH, "r")) {
                    file.seek(lastPosition);
                    String line;
                    while ((line = file.readLine()) != null) {
                        processLogLine(line);   //读取一行计算指标
                    }
                    lastPosition = file.getFilePointer();
                }
            }
        } catch (IOException e) {
            log.error("Error reading nginx log file", e);
        }
    }

    /**
     * 记录请求耗时
     * @param line
     */
    private void processLogLine(String line) {
        try {
            NginxLogEntry entry = parseLogLine(line);  //parseLogLine 正则表达式将值写入对象
            //log.info("获取的值对象:"+entry);
            if (entry != null) {
                // 记录请求延迟
                //requestLatency.record(entry.getRequestTime());  //直接计算指标
                recordLatency(entry.getRequestTime());  //两种记录指标   record 方法时,延迟数据添加到分布统计中
                // 按状态码记录请求
                Timer statusTimer = statusCodeTimers.computeIfAbsent(
                        entry.getStatus(),
                        status -> Timer.builder("nginx_request_status")
                                .tag("status", String.valueOf(status))
                                .register(meterRegistry)
                );
                statusTimer.record(java.time.Duration.ofMillis((long)(entry.getRequestTime() * 1000)));
            }
        } catch (Exception e) {
            log.error("Error processing log line: {}", line, e);
        }
    }

    private NginxLogEntry parseLogLine(String line) {
        Matcher matcher = LOG_PATTERN.matcher(line);
        if (!matcher.matches()) {
            return null;
        }

        NginxLogEntry entry = new NginxLogEntry();
        entry.setRemoteAddr(matcher.group(1));
        entry.setTimeLocal(matcher.group(2));
        entry.setRequest(matcher.group(3));
        entry.setStatus(Integer.parseInt(matcher.group(4)));
        entry.setBodyBytesSent(Long.parseLong(matcher.group(5)));
        entry.setHttpReferer(matcher.group(6));
        entry.setHttpUserAgent(matcher.group(7));
        entry.setHttpXForwardedFor(matcher.group(8));
        entry.setRequestTime(Double.parseDouble(matcher.group(9)));

        return entry;
    }
}
package com.NinxMonitor.conf;

import io.micrometer.core.aop.TimedAspect;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy
public class MetricsConfig {

    @Bean
    public TimedAspect timedAspect(MeterRegistry registry) {
          return new TimedAspect(registry);
    }
}
package com.NinxMonitor.pojo;

import lombok.Data;

@Data
public class NginxLogEntry {
    private String remoteAddr;
    private String timeLocal;
    private String request;
    private int status;
    private long bodyBytesSent;
    private String httpReferer;
    private String httpUserAgent;
    private String httpXForwardedFor;
    private double requestTime;
}
//启动类上加入开启定时任务注解

@EnableScheduling
server:
  port: 9099

spring:
  application:
    name: nginx-monitor

management:
  endpoints:
    web:
      exposure:
        include: prometheus,health,info,metrics
      base-path: /actuator
  endpoint:
    health:
      show-details: always
    prometheus:
      enabled: true
  metrics:
    tags:
      application: ${spring.application.name}   #为所有指标添加应用名称标签
    distribution:
      percentiles-histogram:
        http.server.requests: true   # 启用 HTTP 请求的直方图统计
      sla:
        http.server.requests: 1ms,10ms,100ms,1s,10s
      #percentiles:
       # http.server.requests: 0.5,0.95,0.99  # 额外添加百分位数计算
    export:
      prometheus:
        enabled: true
log:
  path: /Users/zero/Desktop/access.log
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐