• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dev-ignis / cross-log / 17016589983

17 Aug 2025 04:13AM UTC coverage: 87.462% (+1.8%) from 85.706%
17016589983

push

github

dev-ignis
test: - threshold;

673 of 840 branches covered (80.12%)

Branch coverage included in aggregate %.

1071 of 1154 relevant lines covered (92.81%)

393.5 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.89
/src/plugins/api.ts
1
import { PluginContext, ApiPlugin, ApiPluginConfig, PluginInstance } from './types';
2
import { TypedPlugin, PluginFactory } from './plugin-types';
3

4
export class ApiRequestPlugin implements TypedPlugin<'api'> {
30✔
5
  name: 'api' = 'api';
192✔
6
  version = '1.0.0';
192✔
7
  config: ApiPluginConfig;
8
  methods?: ApiPlugin;
9
  private context?: PluginContext;
10
  private sessionId?: string;
11

12
  constructor(config?: ApiPluginConfig) {
13
    this.config = {
192✔
14
      enabled: true,
15
      includeSessionId: true,
16
      includeHeaders: false,
17
      truncateUrl: 100,
18
      ...config
19
    };
20
  }
21

22
  init(context: PluginContext): void {
23
    this.context = context;
192✔
24
    
25
    if (this.config.includeSessionId) {
192✔
26
      this.sessionId = this.generateSessionId();
183✔
27
    }
28

29
    const methods: ApiPlugin = {
192✔
30
      request: this.request.bind(this),
31
      response: this.response.bind(this),
32
      error: this.error.bind(this)
33
    };
34

35
    this.methods = methods;
192✔
36
    (context as any).methods = methods;
192✔
37
    
38
    const instance = context as unknown as PluginInstance;
192✔
39
    instance.methods = methods as any;
192✔
40
  }
41

42
  private generateSessionId(): string {
43
    return `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
183✔
44
  }
45

46
  private truncateUrl(url: string): string {
47
    if (!this.config.truncateUrl || url.length <= this.config.truncateUrl) {
150✔
48
      return url;
144✔
49
    }
50
    return url.substring(0, this.config.truncateUrl) + '...';
6✔
51
  }
52

53
  private formatDuration(duration?: number): string {
54
    if (duration === undefined) return '';
132!
55
    if (duration < 1000) return `${duration}ms`;
132✔
56
    return `${(duration / 1000).toFixed(2)}s`;
6✔
57
  }
58

59
  request(method: string, url: string, duration?: number, status?: number, data?: any): void {
60
    if (!this.context || !this.config.enabled) return;
54!
61

62
    const logData: Record<string, any> = {
54✔
63
      type: 'api_request',
64
      method: method.toUpperCase(),
65
      url: this.truncateUrl(url),
66
      timestamp: new Date().toISOString()
67
    };
68

69
    if (this.config.includeSessionId && this.sessionId) {
54✔
70
      logData.sessionId = this.sessionId;
51✔
71
    }
72

73
    if (duration !== undefined) {
54✔
74
      logData.duration = this.formatDuration(duration);
54✔
75
      logData.durationMs = duration;
54✔
76
    }
77

78
    if (status !== undefined) {
54✔
79
      logData.status = status;
54✔
80
    }
81

82
    if (data !== undefined) {
54!
83
      logData.data = data;
×
84
    }
85

86
    const message = `API Request: ${method.toUpperCase()} ${this.truncateUrl(url)}${
54✔
87
      status ? ` - ${status}` : ''
54!
88
    }${duration !== undefined ? ` (${this.formatDuration(duration)})` : ''}`;
54!
89

90
    if (status && status >= 400) {
54✔
91
      this.context.logger.error(message, 'API', logData);
3✔
92
    } else if (status && status >= 300) {
51!
93
      this.context.logger.warn(message, 'API', logData);
×
94
    } else {
95
      this.context.logger.info(message, 'API', logData);
51✔
96
    }
97
  }
98

99
  response(url: string, status: number, duration?: number, data?: any): void {
100
    if (!this.context || !this.config.enabled) return;
12!
101

102
    const logData: Record<string, any> = {
12✔
103
      type: 'api_response',
104
      url: this.truncateUrl(url),
105
      status,
106
      timestamp: new Date().toISOString()
107
    };
108

109
    if (this.config.includeSessionId && this.sessionId) {
12✔
110
      logData.sessionId = this.sessionId;
12✔
111
    }
112

113
    if (duration !== undefined) {
12✔
114
      logData.duration = this.formatDuration(duration);
12✔
115
      logData.durationMs = duration;
12✔
116
    }
117

118
    if (data !== undefined) {
12!
119
      logData.data = data;
×
120
    }
121

122
    const message = `API Response: ${this.truncateUrl(url)} - ${status}${
12✔
123
      duration !== undefined ? ` (${this.formatDuration(duration)})` : ''
12!
124
    }`;
125

126
    if (status >= 500) {
12✔
127
      this.context.logger.error(message, 'API', logData);
3✔
128
    } else if (status >= 400) {
9✔
129
      this.context.logger.warn(message, 'API', logData);
3✔
130
    } else {
131
      this.context.logger.info(message, 'API', logData);
6✔
132
    }
133
  }
134

135
  error(method: string, url: string, error: Error | string, status?: number): void {
136
    if (!this.context || !this.config.enabled) return;
9!
137

138
    const logData: Record<string, any> = {
9✔
139
      type: 'api_error',
140
      method: method.toUpperCase(),
141
      url: this.truncateUrl(url),
142
      error: error instanceof Error ? {
9✔
143
        message: error.message,
144
        stack: error.stack,
145
        name: error.name
146
      } : error,
147
      timestamp: new Date().toISOString()
148
    };
149

150
    if (this.config.includeSessionId && this.sessionId) {
9✔
151
      logData.sessionId = this.sessionId;
9✔
152
    }
153

154
    if (status !== undefined) {
9✔
155
      logData.status = status;
6✔
156
    }
157

158
    const message = `API Error: ${method.toUpperCase()} ${this.truncateUrl(url)}${
9✔
159
      status ? ` - ${status}` : ''
9✔
160
    } - ${error instanceof Error ? error.message : error}`;
9✔
161

162
    this.context.logger.error(message, 'API', logData);
9✔
163
  }
164

165
  destroy(): void {
166
    this.context = undefined;
21✔
167
    this.sessionId = undefined;
21✔
168
  }
169
}
170

171
export const createApiPlugin: PluginFactory<'api'> = (config?: Partial<ApiPluginConfig>): TypedPlugin<'api'> => {
30✔
172
  return new ApiRequestPlugin(config as ApiPluginConfig);
192✔
173
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc