0
# Service Provider Interface
1
2
SLF4J uses a service provider architecture that allows different logging framework implementations to be plugged in at runtime. The Service Provider Interface (SPI) defines the contracts that logging implementations must fulfill to integrate with SLF4J.
3
4
## Capabilities
5
6
### SLF4J Service Provider
7
8
Main service provider interface for pluggable logging implementations.
9
10
```java { .api }
11
/**
12
* This interface based on ServiceLoader paradigm. It replaces the old static-binding mechanism used in SLF4J versions 1.0.x to 1.7.x.
13
*/
14
public interface SLF4JServiceProvider {
15
/**
16
* Return the instance of ILoggerFactory that LoggerFactory class should bind to
17
* @return instance of ILoggerFactory
18
*/
19
ILoggerFactory getLoggerFactory();
20
21
/**
22
* Return the instance of IMarkerFactory that MarkerFactory class should bind to
23
* @return instance of IMarkerFactory
24
*/
25
IMarkerFactory getMarkerFactory();
26
27
/**
28
* Return the instance of MDCAdapter that MDC should bind to
29
* @return instance of MDCAdapter
30
*/
31
MDCAdapter getMDCAdapter();
32
33
/**
34
* Return the maximum API version for SLF4J that the logging implementation supports
35
* @return the string API version, for example "2.0.1"
36
*/
37
String getRequestedApiVersion();
38
39
/**
40
* Initialize the logging back-end
41
* WARNING: This method is intended to be called once by LoggerFactory class and from nowhere else
42
*/
43
void initialize();
44
}
45
```
46
47
### Logger Factory Interface
48
49
Factory interface for creating Logger instances.
50
51
```java { .api }
52
/**
53
* ILoggerFactory instances manufacture Logger instances by name
54
*/
55
public interface ILoggerFactory {
56
/**
57
* Return an appropriate Logger instance as specified by the name parameter
58
* @param name the name of the Logger to return
59
* @return a Logger instance
60
*/
61
Logger getLogger(String name);
62
}
63
```
64
65
### Marker Factory Interface
66
67
Factory interface for creating and managing Marker instances.
68
69
```java { .api }
70
/**
71
* Implementations of this interface are used to manufacture Marker instances
72
*/
73
public interface IMarkerFactory {
74
/**
75
* Manufacture a Marker instance by name. If the instance has been created earlier, return the previously created instance
76
* @param name the name of the marker to be created, null value is not allowed
77
* @return a Marker instance
78
*/
79
Marker getMarker(String name);
80
81
/**
82
* Checks if the marker with the name already exists
83
* @param name marker name
84
* @return true if the marker exists, false otherwise
85
*/
86
boolean exists(String name);
87
88
/**
89
* Detach an existing marker
90
* @param name The name of the marker to detach
91
* @return whether the marker could be detached or not
92
*/
93
boolean detachMarker(String name);
94
95
/**
96
* Create a marker which is detached from this IMarkerFactory
97
* @param name marker name
98
* @return a marker detached from this factory
99
*/
100
Marker getDetachedMarker(String name);
101
}
102
```
103
104
### Service Provider Discovery
105
106
How SLF4J discovers and loads service providers.
107
108
```java { .api }
109
/**
110
* LoggerFactory methods for service provider management
111
*/
112
public final class LoggerFactory {
113
/**
114
* Explicitly set the provider class via system property
115
*/
116
public static final String PROVIDER_PROPERTY_KEY = "slf4j.provider";
117
118
/**
119
* Return the SLF4JServiceProvider in use
120
* @return provider in use
121
*/
122
static SLF4JServiceProvider getProvider();
123
124
/**
125
* Return the ILoggerFactory instance in use
126
* @return the ILoggerFactory instance in use
127
*/
128
public static ILoggerFactory getILoggerFactory();
129
}
130
```
131
132
**Usage Examples:**
133
134
```java
135
// Implementing a custom service provider
136
public class MyLoggingServiceProvider implements SLF4JServiceProvider {
137
private ILoggerFactory loggerFactory;
138
private IMarkerFactory markerFactory;
139
private MDCAdapter mdcAdapter;
140
141
public MyLoggingServiceProvider() {
142
// Initialize factories during construction
143
this.loggerFactory = new MyLoggerFactory();
144
this.markerFactory = new MyMarkerFactory();
145
this.mdcAdapter = new MyMDCAdapter();
146
}
147
148
@Override
149
public ILoggerFactory getLoggerFactory() {
150
return loggerFactory;
151
}
152
153
@Override
154
public IMarkerFactory getMarkerFactory() {
155
return markerFactory;
156
}
157
158
@Override
159
public MDCAdapter getMDCAdapter() {
160
return mdcAdapter;
161
}
162
163
@Override
164
public String getRequestedApiVersion() {
165
return "2.0.17";
166
}
167
168
@Override
169
public void initialize() {
170
// Perform any necessary initialization
171
System.out.println("MyLoggingServiceProvider initialized");
172
}
173
}
174
175
// Custom logger factory implementation
176
public class MyLoggerFactory implements ILoggerFactory {
177
private final Map<String, Logger> loggerMap = new ConcurrentHashMap<>();
178
179
@Override
180
public Logger getLogger(String name) {
181
return loggerMap.computeIfAbsent(name, this::createLogger);
182
}
183
184
private Logger createLogger(String name) {
185
return new MyLogger(name);
186
}
187
}
188
```
189
190
### Location-Aware Logger Interface
191
192
Extended logger interface that provides location information.
193
194
```java { .api }
195
/**
196
* An optional interface helping integration with logging systems capable of extracting location information
197
*/
198
public interface LocationAwareLogger extends Logger {
199
// Level constants
200
public final static int TRACE_INT = 00;
201
public final static int DEBUG_INT = 10;
202
public final static int INFO_INT = 20;
203
public final static int WARN_INT = 30;
204
public final static int ERROR_INT = 40;
205
206
/**
207
* Printing method which can be used by implementation classes to receive location information
208
* @param marker The marker to be used for this event, may be null
209
* @param fqcn The fully qualified class name of the logger instance
210
* @param level One of the level integers defined in this interface
211
* @param message The message for the log event
212
* @param argArray An array of arguments to be used in conjunction with the message
213
* @param t The throwable associated with the log event, may be null
214
*/
215
public void log(Marker marker, String fqcn, int level, String message, Object[] argArray, Throwable t);
216
}
217
```
218
219
### Caller Boundary Awareness
220
221
Interface for loggers that support caller boundary detection.
222
223
```java { .api }
224
/**
225
* This interface is used by LoggingEventBuilder implementations to determine the caller boundary
226
*/
227
public interface CallerBoundaryAware {
228
/**
229
* Return the caller boundary for this Logger
230
* @return the caller boundary. Null by default.
231
*/
232
default String getCallerBoundary() {
233
return null;
234
}
235
}
236
```
237
238
### Logging Event Awareness
239
240
Interface for loggers that can handle LoggingEvent objects directly.
241
242
```java { .api }
243
/**
244
* Logger implementations which are capable of handling LoggingEvents should implement this interface
245
*/
246
public interface LoggingEventAware {
247
/**
248
* Log a LoggingEvent
249
* @param event the LoggingEvent to log
250
*/
251
void log(LoggingEvent event);
252
}
253
```
254
255
## Built-in Implementations
256
257
### No-Operation Implementations
258
259
Default implementations that perform no actual logging.
260
261
```java { .api }
262
/**
263
* NOPLogger is an implementation of Logger that performs no operation
264
*/
265
public class NOPLogger implements Logger {
266
public String getName() { return "NOP"; }
267
public boolean isTraceEnabled() { return false; }
268
public void trace(String msg) { }
269
// ... all methods do nothing
270
}
271
272
/**
273
* NOPLoggerFactory is a factory for NOPLogger instances
274
*/
275
public class NOPLoggerFactory implements ILoggerFactory {
276
public Logger getLogger(String name) {
277
return NOPLogger.NOP_LOGGER;
278
}
279
}
280
281
/**
282
* This implementation ignores all MDC operations
283
*/
284
public class NOPMDCAdapter implements MDCAdapter {
285
public void put(String key, String val) { }
286
public String get(String key) { return null; }
287
public void remove(String key) { }
288
public void clear() { }
289
// ... all methods do nothing
290
}
291
```
292
293
### Basic Implementations
294
295
Simple working implementations for basic scenarios.
296
297
```java { .api }
298
/**
299
* A simple implementation of MDC using InheritableThreadLocal
300
*/
301
public class BasicMDCAdapter implements MDCAdapter {
302
private InheritableThreadLocal<Map<String, String>> inheritableThreadLocal =
303
new InheritableThreadLocal<Map<String, String>>();
304
305
public void put(String key, String val);
306
public String get(String key);
307
public void remove(String key);
308
public void clear();
309
public Map<String, String> getCopyOfContextMap();
310
public void setContextMap(Map<String, String> contextMap);
311
// ... implementation details
312
}
313
314
/**
315
* BasicMarker holds references to other markers
316
*/
317
public class BasicMarker implements Marker {
318
private final String name;
319
private final List<Marker> referenceList = new ArrayList<>();
320
321
BasicMarker(String name);
322
public String getName();
323
public void add(Marker reference);
324
public boolean remove(Marker reference);
325
// ... implementation details
326
}
327
328
/**
329
* BasicMarkerFactory is a simple implementation of IMarkerFactory
330
*/
331
public class BasicMarkerFactory implements IMarkerFactory {
332
private final Map<String, Marker> markerMap = new ConcurrentHashMap<>();
333
334
public Marker getMarker(String name);
335
public boolean exists(String name);
336
public Marker getDetachedMarker(String name);
337
}
338
```
339
340
### Substitute Implementations
341
342
Temporary implementations used during SLF4J initialization.
343
344
```java { .api }
345
/**
346
* A logger implementation which logs via a delegate logger but can also buffer logging events when the delegate is null
347
*/
348
public class SubstituteLogger implements Logger {
349
private final String name;
350
private volatile Logger _delegate;
351
private final Queue<SubstituteLoggingEvent> eventQueue = new LinkedBlockingQueue<>();
352
353
public SubstituteLogger(String name);
354
public String getName();
355
void setDelegate(Logger delegate);
356
// ... delegates to _delegate when available, queues events otherwise
357
}
358
359
/**
360
* SubstituteLoggerFactory creates SubstituteLogger instances
361
*/
362
public class SubstituteLoggerFactory implements ILoggerFactory {
363
private final Map<String, SubstituteLogger> loggers = new ConcurrentHashMap<>();
364
365
public Logger getLogger(String name);
366
public List<SubstituteLogger> getLoggers();
367
public void postInitialization();
368
public void clear();
369
}
370
```
371
372
## Service Provider Registration
373
374
### Java ServiceLoader
375
376
Register service providers using the standard Java ServiceLoader mechanism:
377
378
1. Create a file named `org.slf4j.spi.SLF4JServiceProvider` in `META-INF/services/`
379
2. Add the fully qualified class name of your service provider implementation
380
381
```
382
# META-INF/services/org.slf4j.spi.SLF4JServiceProvider
383
com.example.logging.MyLoggingServiceProvider
384
```
385
386
### Explicit Provider Configuration
387
388
Override automatic discovery using system property:
389
390
```java
391
// Set system property to explicitly specify provider
392
System.setProperty("slf4j.provider", "com.example.logging.MyLoggingServiceProvider");
393
394
// Or via command line
395
// -Dslf4j.provider=com.example.logging.MyLoggingServiceProvider
396
```
397
398
## Implementation Guidelines
399
400
### Service Provider Implementation
401
402
```java
403
public class CustomServiceProvider implements SLF4JServiceProvider {
404
private static final String REQUESTED_API_VERSION = "2.0.17";
405
406
private final ILoggerFactory loggerFactory;
407
private final IMarkerFactory markerFactory;
408
private final MDCAdapter mdcAdapter;
409
410
public CustomServiceProvider() {
411
// Initialize all components in constructor
412
this.loggerFactory = new CustomLoggerFactory();
413
this.markerFactory = new CustomMarkerFactory();
414
this.mdcAdapter = new CustomMDCAdapter();
415
}
416
417
@Override
418
public ILoggerFactory getLoggerFactory() {
419
return loggerFactory;
420
}
421
422
@Override
423
public IMarkerFactory getMarkerFactory() {
424
return markerFactory;
425
}
426
427
@Override
428
public MDCAdapter getMDCAdapter() {
429
return mdcAdapter;
430
}
431
432
@Override
433
public String getRequestedApiVersion() {
434
return REQUESTED_API_VERSION;
435
}
436
437
@Override
438
public void initialize() {
439
// Perform initialization tasks
440
// This method is called once by LoggerFactory
441
configureLogging();
442
validateConfiguration();
443
}
444
445
private void configureLogging() {
446
// Configure your logging backend
447
}
448
449
private void validateConfiguration() {
450
// Validate configuration and throw exceptions if invalid
451
}
452
}
453
```
454
455
### Logger Implementation
456
457
```java
458
public class CustomLogger implements Logger, LocationAwareLogger, LoggingEventAware {
459
private final String name;
460
private final CustomLoggingBackend backend;
461
462
public CustomLogger(String name, CustomLoggingBackend backend) {
463
this.name = name;
464
this.backend = backend;
465
}
466
467
@Override
468
public String getName() {
469
return name;
470
}
471
472
@Override
473
public boolean isDebugEnabled() {
474
return backend.isLevelEnabled(name, Level.DEBUG);
475
}
476
477
@Override
478
public void debug(String msg) {
479
if (isDebugEnabled()) {
480
backend.log(name, Level.DEBUG, msg, null, null);
481
}
482
}
483
484
@Override
485
public void debug(String format, Object arg) {
486
if (isDebugEnabled()) {
487
FormattingTuple ft = MessageFormatter.format(format, arg);
488
backend.log(name, Level.DEBUG, ft.getMessage(), null, ft.getThrowable());
489
}
490
}
491
492
// LocationAwareLogger implementation
493
@Override
494
public void log(Marker marker, String fqcn, int level, String message, Object[] argArray, Throwable t) {
495
if (backend.isLevelEnabled(name, Level.intToLevel(level))) {
496
backend.log(name, marker, fqcn, Level.intToLevel(level), message, argArray, t);
497
}
498
}
499
500
// LoggingEventAware implementation
501
@Override
502
public void log(LoggingEvent event) {
503
if (backend.isLevelEnabled(name, event.getLevel())) {
504
backend.log(event);
505
}
506
}
507
508
// ... implement all other Logger methods
509
}
510
```
511
512
### MDC Adapter Implementation
513
514
```java
515
public class CustomMDCAdapter implements MDCAdapter {
516
private final ThreadLocal<Map<String, String>> threadLocalMap = new ThreadLocal<>();
517
private final ThreadLocal<Map<String, Deque<String>>> threadLocalDequeMap = new ThreadLocal<>();
518
519
@Override
520
public void put(String key, String val) {
521
if (key == null) {
522
throw new IllegalArgumentException("key cannot be null");
523
}
524
525
Map<String, String> map = threadLocalMap.get();
526
if (map == null) {
527
map = new HashMap<>();
528
threadLocalMap.set(map);
529
}
530
map.put(key, val);
531
}
532
533
@Override
534
public String get(String key) {
535
Map<String, String> map = threadLocalMap.get();
536
return (map != null) ? map.get(key) : null;
537
}
538
539
@Override
540
public void remove(String key) {
541
Map<String, String> map = threadLocalMap.get();
542
if (map != null) {
543
map.remove(key);
544
if (map.isEmpty()) {
545
threadLocalMap.remove();
546
}
547
}
548
}
549
550
@Override
551
public void clear() {
552
threadLocalMap.remove();
553
threadLocalDequeMap.remove();
554
}
555
556
@Override
557
public void pushByKey(String key, String value) {
558
Map<String, Deque<String>> dequeMap = threadLocalDequeMap.get();
559
if (dequeMap == null) {
560
dequeMap = new HashMap<>();
561
threadLocalDequeMap.set(dequeMap);
562
}
563
564
Deque<String> deque = dequeMap.computeIfAbsent(key, k -> new ArrayDeque<>());
565
deque.push(value);
566
}
567
568
@Override
569
public String popByKey(String key) {
570
Map<String, Deque<String>> dequeMap = threadLocalDequeMap.get();
571
if (dequeMap != null) {
572
Deque<String> deque = dequeMap.get(key);
573
if (deque != null && !deque.isEmpty()) {
574
return deque.pop();
575
}
576
}
577
return null;
578
}
579
580
// ... implement remaining methods
581
}
582
```
583
584
## Version Compatibility
585
586
SLF4J service providers should declare their API compatibility:
587
588
```java
589
@Override
590
public String getRequestedApiVersion() {
591
// Declare the SLF4J API version this provider supports
592
return "2.0.17";
593
}
594
```
595
596
Version compatibility matrix:
597
- **"2.0"**: Supports SLF4J 2.0.x features including fluent API
598
- **"1.8"**: Basic SLF4J 1.x compatibility
599
- **"1.7"**: Legacy SLF4J 1.7.x compatibility
600
601
## Multiple Provider Handling
602
603
When multiple providers are found on the classpath:
604
605
1. SLF4J reports the ambiguity with a warning message
606
2. The first provider found is used
607
3. All other providers are ignored
608
4. Applications can override using the `slf4j.provider` system property
609
610
## Migration from SLF4J 1.x Bindings
611
612
SLF4J 2.0 replaces the old static binding mechanism:
613
614
- **Old**: `StaticLoggerBinder` and `StaticMarkerBinder` classes
615
- **New**: `SLF4JServiceProvider` interface with `ServiceLoader`
616
- **Compatibility**: SLF4J 2.0 detects and warns about old-style bindings
617
- **Migration**: Implement `SLF4JServiceProvider` and register via `ServiceLoader`