Implement CheckCle Microservice Operation
A Go-based microservice for service operations including ICMP ping, DNS resolution, and TCP connectivity
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
|
||||
package savers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"service-operation/pocketbase"
|
||||
"service-operation/types"
|
||||
)
|
||||
|
||||
func (ms *MetricsSaver) SaveDNSDataToPocketBase(result *types.OperationResult, serviceID string) {
|
||||
// Create a short, professional status message
|
||||
var details string
|
||||
|
||||
if result.Success {
|
||||
// Success message with record count and query info
|
||||
recordCount := len(result.DNSRecords)
|
||||
details = fmt.Sprintf("✅ DNS %s Query OK - %d records found",
|
||||
strings.ToUpper(result.DNSType), recordCount)
|
||||
|
||||
// Add response time
|
||||
details += fmt.Sprintf(" | Response time: %.2fms",
|
||||
float64(result.ResponseTime.Nanoseconds())/1000000)
|
||||
|
||||
// Add first few records for context
|
||||
if recordCount > 0 {
|
||||
if recordCount <= 2 {
|
||||
details += fmt.Sprintf(" | Records: %s", strings.Join(result.DNSRecords, ", "))
|
||||
} else {
|
||||
details += fmt.Sprintf(" | Records: %s... (+%d more)",
|
||||
strings.Join(result.DNSRecords[:2], ", "), recordCount-2)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Error message with query type
|
||||
details = fmt.Sprintf("❌ DNS %s Query Failed - %s",
|
||||
strings.ToUpper(result.DNSType),
|
||||
GetShortErrorMessage(result.Error))
|
||||
|
||||
// Add response time if available
|
||||
if result.ResponseTime > 0 {
|
||||
details += fmt.Sprintf(" | Response time: %.2fms",
|
||||
float64(result.ResponseTime.Nanoseconds())/1000000)
|
||||
}
|
||||
}
|
||||
|
||||
dnsData := pocketbase.DNSDataRecord{
|
||||
ServiceID: serviceID,
|
||||
Timestamp: time.Now(),
|
||||
ResponseTime: result.ResponseTime.Milliseconds(),
|
||||
Status: GetStatusString(result.Success),
|
||||
QueryType: result.DNSType,
|
||||
ResolveIP: strings.Join(result.DNSRecords, ","),
|
||||
MsgSize: fmt.Sprintf("%d", len(result.DNSRecords)),
|
||||
Question: result.Host,
|
||||
Answer: strings.Join(result.DNSRecords, ","),
|
||||
Authority: "", // Not available in current implementation
|
||||
ErrorMessage: result.Error,
|
||||
Details: details, // Short, clean message
|
||||
RegionName: "default", // You can make this configurable
|
||||
AgentID: "1", // You can make this configurable
|
||||
}
|
||||
|
||||
if err := ms.pbClient.SaveDNSData(dnsData); err != nil {
|
||||
println("Failed to save DNS data to PocketBase:", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Method for monitoring service usage
|
||||
func (ms *MetricsSaver) SaveDNSDataForService(service pocketbase.Service, result *types.OperationResult) {
|
||||
ms.SaveDNSDataToPocketBase(result, service.ID)
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
|
||||
package savers
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"service-operation/pocketbase"
|
||||
"service-operation/types"
|
||||
)
|
||||
|
||||
type MetricsSaver struct {
|
||||
pbClient *pocketbase.PocketBaseClient
|
||||
}
|
||||
|
||||
func NewMetricsSaver(pbClient *pocketbase.PocketBaseClient) *MetricsSaver {
|
||||
return &MetricsSaver{
|
||||
pbClient: pbClient,
|
||||
}
|
||||
}
|
||||
|
||||
func (ms *MetricsSaver) SaveMetricsToPocketBase(result *types.OperationResult, serviceID string) {
|
||||
// Save general metrics using the new structure
|
||||
metrics := pocketbase.MetricsRecord{
|
||||
ServiceName: result.Host,
|
||||
Host: result.Host,
|
||||
Uptime: 0, // This would need to be calculated based on your requirements
|
||||
ResponseTime: result.ResponseTime.Milliseconds(),
|
||||
LastChecked: time.Now().Format(time.RFC3339),
|
||||
Port: result.Port,
|
||||
ServiceType: string(result.Type),
|
||||
Status: GetStatusString(result.Success),
|
||||
ErrorMessage: result.Error,
|
||||
Details: FormatResultDetails(result),
|
||||
CheckedAt: time.Now().Format(time.RFC3339),
|
||||
}
|
||||
|
||||
if err := ms.pbClient.SaveMetrics(metrics); err != nil {
|
||||
// Log error but don't fail the operation
|
||||
println("Failed to save metrics to PocketBase:", err.Error())
|
||||
}
|
||||
|
||||
// Save detailed data based on operation type - only once per check
|
||||
if serviceID != "" {
|
||||
switch result.Type {
|
||||
case types.OperationPing:
|
||||
ms.SavePingDataToPocketBase(result, serviceID)
|
||||
case types.OperationHTTP:
|
||||
ms.SaveUptimeDataToPocketBase(result, serviceID)
|
||||
case types.OperationDNS:
|
||||
ms.SaveDNSDataToPocketBase(result, serviceID)
|
||||
case types.OperationTCP:
|
||||
ms.SaveTCPDataToPocketBase(result, serviceID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Primary method for monitoring service usage - this prevents duplicates
|
||||
func (ms *MetricsSaver) SaveMetricsForService(service pocketbase.Service, result *types.OperationResult) {
|
||||
// Save general metrics first - reduced logging
|
||||
metrics := pocketbase.MetricsRecord{
|
||||
ServiceName: service.Name,
|
||||
Host: service.Host,
|
||||
Uptime: 0, // This would need to be calculated based on your requirements
|
||||
ResponseTime: result.ResponseTime.Milliseconds(),
|
||||
LastChecked: time.Now().Format(time.RFC3339),
|
||||
Port: service.Port,
|
||||
ServiceType: service.ServiceType,
|
||||
Status: GetStatusString(result.Success),
|
||||
ErrorMessage: result.Error,
|
||||
Details: FormatResultDetails(result),
|
||||
CheckedAt: time.Now().Format(time.RFC3339),
|
||||
}
|
||||
|
||||
if err := ms.pbClient.SaveMetrics(metrics); err != nil {
|
||||
// Silent error - no logging to reduce output
|
||||
return
|
||||
}
|
||||
|
||||
// Save detailed data based on service type - only once per service with minimal logging
|
||||
switch service.ServiceType {
|
||||
case "ping", "icmp":
|
||||
ms.SavePingDataToPocketBase(result, service.ID)
|
||||
case "dns":
|
||||
ms.SaveDNSDataToPocketBase(result, service.ID)
|
||||
case "http", "https":
|
||||
ms.SaveUptimeDataToPocketBase(result, service.ID)
|
||||
case "tcp":
|
||||
ms.SaveTCPDataToPocketBase(result, service.ID)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
|
||||
package savers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"service-operation/pocketbase"
|
||||
"service-operation/types"
|
||||
)
|
||||
|
||||
func (ms *MetricsSaver) SavePingDataToPocketBase(result *types.OperationResult, serviceID string) {
|
||||
// Create a short, professional status message
|
||||
var details string
|
||||
|
||||
if result.Success {
|
||||
// Success message with packet stats
|
||||
details = fmt.Sprintf("✅ Ping OK - %d/%d packets received",
|
||||
result.PacketsRecv, result.PacketsSent)
|
||||
|
||||
// Add loss percentage if there was any loss
|
||||
if result.PacketLoss > 0 {
|
||||
details += fmt.Sprintf(" (%.1f%% loss)", result.PacketLoss)
|
||||
}
|
||||
|
||||
// Add response time info
|
||||
details += fmt.Sprintf(" | Avg: %.2fms",
|
||||
float64(result.AvgRTT.Nanoseconds())/1000000)
|
||||
|
||||
// Add min/max if different from average (significant variance)
|
||||
if result.MinRTT != result.MaxRTT {
|
||||
details += fmt.Sprintf(", Min: %.2fms, Max: %.2fms",
|
||||
float64(result.MinRTT.Nanoseconds())/1000000,
|
||||
float64(result.MaxRTT.Nanoseconds())/1000000)
|
||||
}
|
||||
} else {
|
||||
// Error message
|
||||
if result.PacketLoss >= 100 {
|
||||
details = fmt.Sprintf("❌ Ping Failed - 100%% packet loss (%s)",
|
||||
GetShortErrorMessage(result.Error))
|
||||
} else {
|
||||
details = fmt.Sprintf("⚠️ Ping Partial - %.1f%% packet loss (%s)",
|
||||
result.PacketLoss, GetShortErrorMessage(result.Error))
|
||||
}
|
||||
}
|
||||
|
||||
pingData := pocketbase.PingDataRecord{
|
||||
ServiceID: serviceID,
|
||||
Timestamp: time.Now(),
|
||||
ResponseTime: result.ResponseTime.Milliseconds(),
|
||||
Status: GetStatusString(result.Success),
|
||||
PacketsSent: fmt.Sprintf("%d", result.PacketsSent),
|
||||
PacketsRecv: fmt.Sprintf("%d", result.PacketsRecv),
|
||||
PacketLoss: fmt.Sprintf("%.1f%%", result.PacketLoss),
|
||||
MinRTT: fmt.Sprintf("%.2fms", float64(result.MinRTT.Nanoseconds())/1000000),
|
||||
MaxRTT: fmt.Sprintf("%.2fms", float64(result.MaxRTT.Nanoseconds())/1000000),
|
||||
AvgRTT: fmt.Sprintf("%.2fms", float64(result.AvgRTT.Nanoseconds())/1000000),
|
||||
RTTs: "", // Not currently tracked
|
||||
Latency: fmt.Sprintf("%.2fms", float64(result.AvgRTT.Nanoseconds())/1000000),
|
||||
ErrorMessage: result.Error,
|
||||
Details: details, // Short, clean message
|
||||
}
|
||||
|
||||
if err := ms.pbClient.SavePingData(pingData); err != nil {
|
||||
println("Failed to save ping data to PocketBase:", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Method for monitoring service usage
|
||||
func (ms *MetricsSaver) SavePingDataForService(service pocketbase.Service, result *types.OperationResult) {
|
||||
ms.SavePingDataToPocketBase(result, service.ID)
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
|
||||
package savers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"service-operation/pocketbase"
|
||||
"service-operation/types"
|
||||
)
|
||||
|
||||
func (ms *MetricsSaver) SaveTCPDataToPocketBase(result *types.OperationResult, serviceID string) {
|
||||
// Create a short, professional status message
|
||||
var details string
|
||||
|
||||
if result.Success && result.TCPConnected {
|
||||
// Success message with connection info
|
||||
details = fmt.Sprintf("✅ TCP Connection OK - Port %d accessible", result.Port)
|
||||
|
||||
// Add response time
|
||||
details += fmt.Sprintf(" | Connection time: %.2fms",
|
||||
float64(result.ResponseTime.Nanoseconds())/1000000)
|
||||
} else {
|
||||
// Error message with port info
|
||||
details = fmt.Sprintf("❌ TCP Connection Failed - Port %d unreachable", result.Port)
|
||||
|
||||
if result.Error != "" {
|
||||
details += fmt.Sprintf(" (%s)", GetShortErrorMessage(result.Error))
|
||||
}
|
||||
|
||||
// Add response time if available
|
||||
if result.ResponseTime > 0 {
|
||||
details += fmt.Sprintf(" | Timeout: %.2fms",
|
||||
float64(result.ResponseTime.Nanoseconds())/1000000)
|
||||
}
|
||||
}
|
||||
|
||||
connectionStatus := "disconnected"
|
||||
if result.TCPConnected {
|
||||
connectionStatus = "connected"
|
||||
}
|
||||
|
||||
tcpData := pocketbase.TCPDataRecord{
|
||||
ServiceID: serviceID,
|
||||
Timestamp: time.Now(),
|
||||
ResponseTime: result.ResponseTime.Milliseconds(),
|
||||
Status: GetStatusString(result.Success),
|
||||
Connection: connectionStatus,
|
||||
Latency: fmt.Sprintf("%.2fms", float64(result.ResponseTime.Nanoseconds())/1000000),
|
||||
Port: strconv.Itoa(result.Port),
|
||||
ErrorMessage: result.Error,
|
||||
Details: details,
|
||||
RegionName: "default",
|
||||
AgentID: "1",
|
||||
}
|
||||
|
||||
if err := ms.pbClient.SaveTCPData(tcpData); err != nil {
|
||||
fmt.Printf("Failed to save TCP data to PocketBase: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Method for monitoring service usage
|
||||
func (ms *MetricsSaver) SaveTCPDataForService(service pocketbase.Service, result *types.OperationResult) {
|
||||
ms.SaveTCPDataToPocketBase(result, service.ID)
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
|
||||
package savers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"service-operation/pocketbase"
|
||||
"service-operation/types"
|
||||
)
|
||||
|
||||
func (ms *MetricsSaver) SaveUptimeDataToPocketBase(result *types.OperationResult, serviceID string) {
|
||||
// Create a short, professional status message
|
||||
var details string
|
||||
|
||||
if result.Success {
|
||||
// Success message with basic info
|
||||
details = fmt.Sprintf("✅ HTTP %d OK - Response time: %.2fms",
|
||||
result.HTTPStatusCode,
|
||||
float64(result.ResponseTime.Nanoseconds())/1000000)
|
||||
|
||||
// Add content info if available
|
||||
if result.ContentLength > 0 {
|
||||
details += fmt.Sprintf(" | Content: %s", FormatBytes(result.ContentLength))
|
||||
}
|
||||
|
||||
// Add server info if available
|
||||
if server, exists := result.HTTPHeaders["Server"]; exists {
|
||||
details += fmt.Sprintf(" | Server: %s", server)
|
||||
}
|
||||
} else {
|
||||
// Error message with status code if available
|
||||
if result.HTTPStatusCode > 0 {
|
||||
details = fmt.Sprintf("❌ HTTP %d Error - %s",
|
||||
result.HTTPStatusCode,
|
||||
GetShortErrorMessage(result.Error))
|
||||
} else {
|
||||
details = fmt.Sprintf("🔌 Connection Error - %s",
|
||||
GetShortErrorMessage(result.Error))
|
||||
}
|
||||
|
||||
// Add response time if available
|
||||
if result.ResponseTime > 0 {
|
||||
details += fmt.Sprintf(" | Response time: %.2fms",
|
||||
float64(result.ResponseTime.Nanoseconds())/1000000)
|
||||
}
|
||||
}
|
||||
|
||||
uptimeData := pocketbase.UptimeDataRecord{
|
||||
ServiceID: serviceID,
|
||||
Timestamp: time.Now(),
|
||||
ResponseTime: result.ResponseTime.Milliseconds(),
|
||||
Status: GetStatusString(result.Success),
|
||||
Packets: "N/A", // Not applicable for HTTP
|
||||
Latency: fmt.Sprintf("%.2fms", float64(result.ResponseTime.Nanoseconds())/1000000),
|
||||
StatusCodes: fmt.Sprintf("%d", result.HTTPStatusCode),
|
||||
Keyword: "", // Can be populated later if needed
|
||||
ErrorMessage: result.Error,
|
||||
Details: details, // Short, clean message
|
||||
Region: "default", // You can make this configurable
|
||||
RegionID: "1", // You can make this configurable
|
||||
}
|
||||
|
||||
if err := ms.pbClient.SaveUptimeData(uptimeData); err != nil {
|
||||
println("Failed to save uptime data to PocketBase:", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Method for monitoring service usage
|
||||
func (ms *MetricsSaver) SaveUptimeDataForService(service pocketbase.Service, result *types.OperationResult) {
|
||||
ms.SaveUptimeDataToPocketBase(result, service.ID)
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
|
||||
package savers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"service-operation/types"
|
||||
)
|
||||
|
||||
// Helper function to format bytes in a readable way
|
||||
func FormatBytes(bytes int64) string {
|
||||
if bytes < 1024 {
|
||||
return fmt.Sprintf("%d B", bytes)
|
||||
} else if bytes < 1024*1024 {
|
||||
return fmt.Sprintf("%.1f KB", float64(bytes)/1024)
|
||||
} else {
|
||||
return fmt.Sprintf("%.1f MB", float64(bytes)/(1024*1024))
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to create short error messages
|
||||
func GetShortErrorMessage(errorMessage string) string {
|
||||
if errorMessage == "" {
|
||||
return "Unknown error"
|
||||
}
|
||||
|
||||
errorLower := strings.ToLower(errorMessage)
|
||||
|
||||
if strings.Contains(errorLower, "timeout") {
|
||||
return "Request timeout"
|
||||
} else if strings.Contains(errorLower, "connection refused") {
|
||||
return "Connection refused"
|
||||
} else if strings.Contains(errorLower, "dns") || strings.Contains(errorLower, "no such host") {
|
||||
return "DNS resolution failed"
|
||||
} else if strings.Contains(errorLower, "certificate") || strings.Contains(errorLower, "ssl") || strings.Contains(errorLower, "tls") {
|
||||
return "SSL certificate error"
|
||||
} else if strings.Contains(errorLower, "server error") || strings.Contains(errorLower, "internal server error") {
|
||||
return "Internal server error"
|
||||
} else if strings.Contains(errorLower, "not found") {
|
||||
return "Page not found"
|
||||
} else if strings.Contains(errorLower, "unauthorized") {
|
||||
return "Unauthorized access"
|
||||
} else if strings.Contains(errorLower, "forbidden") {
|
||||
return "Access forbidden"
|
||||
}
|
||||
|
||||
// For other errors, take first 50 characters and clean it up
|
||||
shortMsg := errorMessage
|
||||
if len(shortMsg) > 50 {
|
||||
shortMsg = shortMsg[:50] + "..."
|
||||
}
|
||||
|
||||
return shortMsg
|
||||
}
|
||||
|
||||
func GetStatusString(success bool) string {
|
||||
if success {
|
||||
return "up"
|
||||
}
|
||||
return "down"
|
||||
}
|
||||
|
||||
func FormatResultDetails(result *types.OperationResult) string {
|
||||
// This can be expanded based on operation type
|
||||
if result.Details != "" {
|
||||
return result.Details
|
||||
}
|
||||
|
||||
switch result.Type {
|
||||
case types.OperationPing:
|
||||
if result.Success {
|
||||
return fmt.Sprintf("Ping successful - %d packets sent, %d received", result.PacketsSent, result.PacketsRecv)
|
||||
}
|
||||
return fmt.Sprintf("Ping failed - %s", result.Error)
|
||||
case types.OperationHTTP:
|
||||
if result.Success {
|
||||
return fmt.Sprintf("HTTP %d - Response time: %.2fms", result.HTTPStatusCode, float64(result.ResponseTime.Nanoseconds())/1000000)
|
||||
}
|
||||
return fmt.Sprintf("HTTP failed - %s", result.Error)
|
||||
case types.OperationDNS:
|
||||
if result.Success {
|
||||
return fmt.Sprintf("DNS %s query successful - %d records found", result.DNSType, len(result.DNSRecords))
|
||||
}
|
||||
return fmt.Sprintf("DNS query failed - %s", result.Error)
|
||||
default:
|
||||
return "Operation completed"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user