0
# Service Provider Interface
1
2
Extension points for custom logger implementations, context factories, and integration with different logging backends. Enables Log4j API to work with various implementations.
3
4
## Capabilities
5
6
### LoggerContext Interface
7
8
Anchor point for logging implementations that manages loggers and their lifecycle.
9
10
```java { .api }
11
/**
12
* Anchor point for logging implementations; manages loggers
13
*/
14
public interface LoggerContext {
15
/** Get logger by name */
16
ExtendedLogger getLogger(String name);
17
18
/** Get logger with custom message factory */
19
ExtendedLogger getLogger(String name, MessageFactory messageFactory);
20
21
/** Get logger by class */
22
ExtendedLogger getLogger(Class<?> cls);
23
24
/** Check if logger exists */
25
boolean hasLogger(String name);
26
27
/** Check if logger exists with specific message factory */
28
boolean hasLogger(String name, MessageFactory messageFactory);
29
30
/** Check if logger exists by class */
31
boolean hasLogger(Class<?> cls);
32
33
/** Get external context object */
34
Object getExternalContext();
35
36
/** Get stored object by key */
37
Object getObject(String key);
38
39
/** Store object with key */
40
Object putObject(String key, Object value);
41
42
/** Remove stored object */
43
Object removeObject(String key);
44
45
/** Empty array constant */
46
LoggerContext[] EMPTY_ARRAY = new LoggerContext[0];
47
}
48
```
49
50
**Usage Examples:**
51
52
```java
53
public void demonstrateLoggerContext() {
54
// Get current logger context
55
LoggerContext context = LogManager.getContext();
56
57
// Get loggers from context
58
ExtendedLogger logger1 = context.getLogger("com.example.MyClass");
59
ExtendedLogger logger2 = context.getLogger(MyClass.class);
60
61
// Check logger existence
62
boolean exists = context.hasLogger("com.example.MyClass");
63
64
// Store and retrieve context-specific objects
65
context.putObject("config", new MyConfiguration());
66
MyConfiguration config = (MyConfiguration) context.getObject("config");
67
68
// External context (implementation-specific)
69
Object externalCtx = context.getExternalContext();
70
}
71
72
// Custom logger context implementation
73
public class CustomLoggerContext implements LoggerContext {
74
private final Map<String, ExtendedLogger> loggers = new ConcurrentHashMap<>();
75
private final Map<String, Object> objects = new ConcurrentHashMap<>();
76
private final Object externalContext;
77
78
public CustomLoggerContext(Object externalContext) {
79
this.externalContext = externalContext;
80
}
81
82
@Override
83
public ExtendedLogger getLogger(String name) {
84
return loggers.computeIfAbsent(name, this::createLogger);
85
}
86
87
@Override
88
public ExtendedLogger getLogger(String name, MessageFactory messageFactory) {
89
String key = name + "#" + messageFactory.getClass().getName();
90
return loggers.computeIfAbsent(key, k -> createLogger(name, messageFactory));
91
}
92
93
@Override
94
public boolean hasLogger(String name) {
95
return loggers.containsKey(name);
96
}
97
98
@Override
99
public Object getExternalContext() {
100
return externalContext;
101
}
102
103
@Override
104
public Object getObject(String key) {
105
return objects.get(key);
106
}
107
108
@Override
109
public Object putObject(String key, Object value) {
110
return objects.put(key, value);
111
}
112
113
@Override
114
public Object removeObject(String key) {
115
return objects.remove(key);
116
}
117
118
private ExtendedLogger createLogger(String name) {
119
return new CustomExtendedLogger(name);
120
}
121
122
private ExtendedLogger createLogger(String name, MessageFactory messageFactory) {
123
return new CustomExtendedLogger(name, messageFactory);
124
}
125
}
126
```
127
128
### LoggerContextFactory Interface
129
130
Factory for creating and managing LoggerContext instances across different classloaders and configurations.
131
132
```java { .api }
133
/**
134
* Factory for creating LoggerContext instances
135
*/
136
public interface LoggerContextFactory {
137
/**
138
* Get context with full control over selection criteria
139
* @param fqcn Fully qualified class name of the caller
140
* @param loader ClassLoader to associate with context
141
* @param externalContext External context object
142
* @param currentContext Whether to return current context
143
*/
144
LoggerContext getContext(String fqcn, ClassLoader loader,
145
Object externalContext, boolean currentContext);
146
147
/**
148
* Get context with configuration location and name
149
* @param fqcn Fully qualified class name of the caller
150
* @param loader ClassLoader to associate with context
151
* @param externalContext External context object
152
* @param currentContext Whether to return current context
153
* @param configLocation Configuration file location
154
* @param name Context name
155
*/
156
LoggerContext getContext(String fqcn, ClassLoader loader,
157
Object externalContext, boolean currentContext,
158
URI configLocation, String name);
159
160
/** Remove a context */
161
void removeContext(LoggerContext context);
162
163
/** Check if context exists */
164
boolean hasContext(String fqcn, ClassLoader loader, boolean currentContext);
165
166
/**
167
* Shutdown contexts
168
* @param fqcn Fully qualified class name
169
* @param loader ClassLoader
170
* @param currentContext Whether to shutdown current context
171
* @param allContexts Whether to shutdown all contexts
172
*/
173
void shutdown(String fqcn, ClassLoader loader,
174
boolean currentContext, boolean allContexts);
175
176
/** Check if factory is dependent on ClassLoader */
177
boolean isClassLoaderDependent();
178
}
179
```
180
181
**Usage Examples:**
182
183
```java
184
// Custom logger context factory
185
public class CustomLoggerContextFactory implements LoggerContextFactory {
186
private final Map<String, LoggerContext> contexts = new ConcurrentHashMap<>();
187
188
@Override
189
public LoggerContext getContext(String fqcn, ClassLoader loader,
190
Object externalContext, boolean currentContext) {
191
String key = createContextKey(loader, externalContext);
192
return contexts.computeIfAbsent(key, k -> createContext(externalContext));
193
}
194
195
@Override
196
public LoggerContext getContext(String fqcn, ClassLoader loader,
197
Object externalContext, boolean currentContext,
198
URI configLocation, String name) {
199
String key = createContextKey(loader, externalContext, configLocation, name);
200
return contexts.computeIfAbsent(key, k -> createContext(externalContext, configLocation, name));
201
}
202
203
@Override
204
public void removeContext(LoggerContext context) {
205
contexts.values().remove(context);
206
}
207
208
@Override
209
public boolean hasContext(String fqcn, ClassLoader loader, boolean currentContext) {
210
String key = createContextKey(loader, null);
211
return contexts.containsKey(key);
212
}
213
214
@Override
215
public void shutdown(String fqcn, ClassLoader loader,
216
boolean currentContext, boolean allContexts) {
217
if (allContexts) {
218
contexts.clear();
219
} else {
220
String key = createContextKey(loader, null);
221
contexts.remove(key);
222
}
223
}
224
225
@Override
226
public boolean isClassLoaderDependent() {
227
return true; // This factory creates different contexts per ClassLoader
228
}
229
230
private String createContextKey(ClassLoader loader, Object externalContext) {
231
return loader.toString() + "#" + (externalContext != null ? externalContext.toString() : "null");
232
}
233
234
private String createContextKey(ClassLoader loader, Object externalContext,
235
URI configLocation, String name) {
236
return createContextKey(loader, externalContext) + "#" + configLocation + "#" + name;
237
}
238
239
private LoggerContext createContext(Object externalContext) {
240
return new CustomLoggerContext(externalContext);
241
}
242
243
private LoggerContext createContext(Object externalContext, URI configLocation, String name) {
244
return new CustomLoggerContext(externalContext, configLocation, name);
245
}
246
}
247
248
// Usage in application
249
public class ContextFactoryUsage {
250
251
public void demonstrateContextFactory() {
252
LoggerContextFactory factory = new CustomLoggerContextFactory();
253
254
// Get context for current classloader
255
LoggerContext context1 = factory.getContext(
256
this.getClass().getName(),
257
Thread.currentThread().getContextClassLoader(),
258
null,
259
true
260
);
261
262
// Get context with specific configuration
263
URI configUri = URI.create("classpath:log4j2-test.xml");
264
LoggerContext context2 = factory.getContext(
265
this.getClass().getName(),
266
getClass().getClassLoader(),
267
"test-app",
268
false,
269
configUri,
270
"test-context"
271
);
272
273
// Check context existence
274
boolean exists = factory.hasContext(
275
this.getClass().getName(),
276
getClass().getClassLoader(),
277
true
278
);
279
280
// Shutdown specific context
281
factory.removeContext(context1);
282
283
// Shutdown all contexts
284
factory.shutdown(null, null, false, true);
285
}
286
}
287
```
288
289
### ExtendedLogger Interface
290
291
Extended logger interface providing additional methods for logger implementations.
292
293
```java { .api }
294
/**
295
* Extended logger interface for implementations
296
*/
297
public interface ExtendedLogger extends Logger {
298
/**
299
* Log message if the specified level is enabled
300
* @param fqcn Fully qualified class name of the caller
301
* @param level Log level
302
* @param marker Marker
303
* @param message Message
304
* @param throwable Throwable
305
*/
306
void logIfEnabled(String fqcn, Level level, Marker marker,
307
String message, Throwable throwable);
308
309
/** Log with message supplier if level enabled */
310
void logIfEnabled(String fqcn, Level level, Marker marker,
311
Supplier<?> messageSupplier, Throwable throwable);
312
313
/** Log with message object if level enabled */
314
void logIfEnabled(String fqcn, Level level, Marker marker,
315
Object message, Throwable throwable);
316
317
/** Log with Message object if level enabled */
318
void logIfEnabled(String fqcn, Level level, Marker marker,
319
Message message, Throwable throwable);
320
321
/** Log parameterized message if level enabled */
322
void logIfEnabled(String fqcn, Level level, Marker marker,
323
String message, Object... params);
324
325
/** Log parameterized message with specific parameter count if level enabled */
326
void logIfEnabled(String fqcn, Level level, Marker marker,
327
String message, Object p0);
328
void logIfEnabled(String fqcn, Level level, Marker marker,
329
String message, Object p0, Object p1);
330
// ... additional overloads for performance
331
332
/** Always log regardless of level (for internal use) */
333
void logMessage(String fqcn, Level level, Marker marker,
334
Message message, Throwable throwable);
335
}
336
```
337
338
**Usage Examples:**
339
340
```java
341
// Custom extended logger implementation
342
public class CustomExtendedLogger extends AbstractLogger implements ExtendedLogger {
343
private final String name;
344
private final Level level;
345
346
public CustomExtendedLogger(String name) {
347
this.name = name;
348
this.level = Level.INFO; // Default level
349
}
350
351
@Override
352
public void logIfEnabled(String fqcn, Level level, Marker marker,
353
String message, Throwable throwable) {
354
if (isEnabled(level, marker)) {
355
logMessage(fqcn, level, marker,
356
getMessageFactory().newMessage(message), throwable);
357
}
358
}
359
360
@Override
361
public void logIfEnabled(String fqcn, Level level, Marker marker,
362
String message, Object... params) {
363
if (isEnabled(level, marker)) {
364
logMessage(fqcn, level, marker,
365
getMessageFactory().newMessage(message, params), null);
366
}
367
}
368
369
@Override
370
public void logMessage(String fqcn, Level level, Marker marker,
371
Message message, Throwable throwable) {
372
// Actual logging implementation
373
StringBuilder sb = new StringBuilder();
374
sb.append("[").append(level).append("] ");
375
sb.append(name).append(" - ");
376
if (marker != null) {
377
sb.append(marker.getName()).append(" ");
378
}
379
sb.append(message.getFormattedMessage());
380
381
System.out.println(sb.toString());
382
383
if (throwable != null) {
384
throwable.printStackTrace(System.out);
385
}
386
}
387
388
@Override
389
public boolean isEnabled(Level level, Marker marker) {
390
return level.isMoreSpecificThan(this.level);
391
}
392
393
@Override
394
public Level getLevel() {
395
return level;
396
}
397
398
@Override
399
public String getName() {
400
return name;
401
}
402
}
403
404
// Usage of extended logger features
405
public class ExtendedLoggerUsage {
406
private static final String FQCN = ExtendedLoggerUsage.class.getName();
407
408
public void demonstrateExtendedLogger() {
409
ExtendedLogger logger = (ExtendedLogger) LogManager.getLogger();
410
411
// Use extended logger methods directly
412
logger.logIfEnabled(FQCN, Level.INFO, null, "Direct extended logging", (Throwable) null);
413
414
// Parameterized logging with FQCN
415
logger.logIfEnabled(FQCN, Level.DEBUG, null, "User {} performed action {}", "alice", "login");
416
417
// With marker and throwable
418
Marker securityMarker = MarkerManager.getMarker("SECURITY");
419
Exception ex = new RuntimeException("test");
420
logger.logIfEnabled(FQCN, Level.ERROR, securityMarker, "Security violation", ex);
421
422
// Force logging regardless of level (use with caution)
423
Message forceMsg = new SimpleMessage("Forced log message");
424
logger.logMessage(FQCN, Level.TRACE, null, forceMsg, null);
425
}
426
}
427
```
428
429
### AbstractLogger Base Class
430
431
Abstract base class providing common logger functionality that implementations can extend.
432
433
```java { .api }
434
/**
435
* Abstract base class for Logger implementations
436
*/
437
public abstract class AbstractLogger implements ExtendedLogger, Serializable {
438
/** Default message factory */
439
public static final MessageFactory DEFAULT_MESSAGE_FACTORY = ParameterizedMessageFactory.INSTANCE;
440
441
/** Default flow message factory */
442
public static final FlowMessageFactory DEFAULT_FLOW_MESSAGE_FACTORY = DefaultFlowMessageFactory.INSTANCE;
443
444
/** Get message factory for this logger */
445
@Override
446
public MessageFactory getMessageFactory() {
447
return DEFAULT_MESSAGE_FACTORY;
448
}
449
450
/** Get flow message factory for this logger */
451
@Override
452
public FlowMessageFactory getFlowMessageFactory() {
453
return DEFAULT_FLOW_MESSAGE_FACTORY;
454
}
455
456
// Abstract methods that implementations must provide
457
@Override
458
public abstract Level getLevel();
459
460
@Override
461
public abstract boolean isEnabled(Level level, Marker marker, String message, Throwable throwable);
462
463
@Override
464
public abstract boolean isEnabled(Level level, Marker marker, String message);
465
466
@Override
467
public abstract boolean isEnabled(Level level, Marker marker, String message, Object... params);
468
469
@Override
470
public abstract boolean isEnabled(Level level, Marker marker, Object message, Throwable throwable);
471
472
@Override
473
public abstract boolean isEnabled(Level level, Marker marker, Message message, Throwable throwable);
474
475
@Override
476
public abstract void logMessage(String fqcn, Level level, Marker marker,
477
Message message, Throwable throwable);
478
}
479
```
480
481
**Usage Examples:**
482
483
```java
484
// Implementing a custom logger by extending AbstractLogger
485
public class FileLogger extends AbstractLogger {
486
private final String name;
487
private final Level level;
488
private final PrintWriter writer;
489
490
public FileLogger(String name, Level level, String filename) throws IOException {
491
this.name = name;
492
this.level = level;
493
this.writer = new PrintWriter(new FileWriter(filename, true));
494
}
495
496
@Override
497
public String getName() {
498
return name;
499
}
500
501
@Override
502
public Level getLevel() {
503
return level;
504
}
505
506
@Override
507
public boolean isEnabled(Level level, Marker marker, String message, Throwable throwable) {
508
return level.isMoreSpecificThan(this.level);
509
}
510
511
@Override
512
public boolean isEnabled(Level level, Marker marker, String message) {
513
return level.isMoreSpecificThan(this.level);
514
}
515
516
@Override
517
public boolean isEnabled(Level level, Marker marker, String message, Object... params) {
518
return level.isMoreSpecificThan(this.level);
519
}
520
521
@Override
522
public boolean isEnabled(Level level, Marker marker, Object message, Throwable throwable) {
523
return level.isMoreSpecificThan(this.level);
524
}
525
526
@Override
527
public boolean isEnabled(Level level, Marker marker, Message message, Throwable throwable) {
528
return level.isMoreSpecificThan(this.level);
529
}
530
531
@Override
532
public void logMessage(String fqcn, Level level, Marker marker,
533
Message message, Throwable throwable) {
534
synchronized (writer) {
535
writer.printf("[%s] %s %s - %s%n",
536
new Date(), level, name, message.getFormattedMessage());
537
538
if (throwable != null) {
539
throwable.printStackTrace(writer);
540
}
541
542
writer.flush();
543
}
544
}
545
546
public void close() {
547
writer.close();
548
}
549
}
550
551
// Usage of custom logger
552
public class CustomLoggerUsage {
553
554
public void demonstrateCustomLogger() throws IOException {
555
// Create custom file logger
556
FileLogger fileLogger = new FileLogger("com.example.FileLogger", Level.DEBUG, "app.log");
557
558
// Use it like any other logger
559
fileLogger.info("Application started");
560
fileLogger.debug("Debug information: {}", "debug data");
561
fileLogger.error("An error occurred", new RuntimeException("test error"));
562
563
// Clean up
564
fileLogger.close();
565
}
566
}
567
```
568
569
### ThreadContext SPI Interfaces
570
571
Service provider interfaces for custom ThreadContext implementations.
572
573
```java { .api }
574
/**
575
* Interface for ThreadContext map implementations
576
*/
577
public interface ThreadContextMap {
578
/** Clear the context */
579
void clear();
580
581
/** Check if key exists */
582
boolean containsKey(String key);
583
584
/** Get value by key */
585
String get(String key);
586
587
/** Get copy of context as Map */
588
Map<String, String> getCopy();
589
590
/** Get immutable view of context */
591
Map<String, String> getImmutableMapOrNull();
592
593
/** Check if context is empty */
594
boolean isEmpty();
595
596
/** Put key-value pair */
597
void put(String key, String value);
598
599
/** Remove key */
600
void remove(String key);
601
}
602
603
/**
604
* Interface for ThreadContext stack implementations
605
*/
606
public interface ThreadContextStack extends ThreadContext.ContextStack {
607
/** Clear the stack */
608
void clear();
609
610
/** Check if stack contains value */
611
boolean contains(Object o);
612
613
/** Copy the stack */
614
ThreadContextStack copy();
615
616
/** Check if stack equals another */
617
boolean equals(Object obj);
618
619
/** Get stack depth */
620
int getDepth();
621
622
/** Get hash code */
623
int hashCode();
624
625
/** Check if stack is empty */
626
boolean isEmpty();
627
628
/** Get iterator */
629
Iterator<String> iterator();
630
631
/** Peek at top element */
632
String peek();
633
634
/** Pop top element */
635
String pop();
636
637
/** Push element */
638
void push(String message);
639
640
/** Get stack size */
641
int size();
642
643
/** Trim to specified depth */
644
void trim(int depth);
645
}
646
```
647
648
**Usage Examples:**
649
650
```java
651
// Custom ThreadContext map implementation
652
public class DatabaseThreadContextMap implements ThreadContextMap {
653
private final ThreadLocal<Map<String, String>> localMap =
654
ThreadLocal.withInitial(HashMap::new);
655
656
@Override
657
public void clear() {
658
localMap.get().clear();
659
// Could also persist to database here
660
}
661
662
@Override
663
public boolean containsKey(String key) {
664
return localMap.get().containsKey(key);
665
}
666
667
@Override
668
public String get(String key) {
669
return localMap.get().get(key);
670
}
671
672
@Override
673
public Map<String, String> getCopy() {
674
return new HashMap<>(localMap.get());
675
}
676
677
@Override
678
public Map<String, String> getImmutableMapOrNull() {
679
Map<String, String> map = localMap.get();
680
return map.isEmpty() ? null : Collections.unmodifiableMap(map);
681
}
682
683
@Override
684
public boolean isEmpty() {
685
return localMap.get().isEmpty();
686
}
687
688
@Override
689
public void put(String key, String value) {
690
localMap.get().put(key, value);
691
// Could also persist to database here
692
persistToDatabase(key, value);
693
}
694
695
@Override
696
public void remove(String key) {
697
localMap.get().remove(key);
698
// Could also remove from database here
699
removeFromDatabase(key);
700
}
701
702
private void persistToDatabase(String key, String value) {
703
// Database persistence logic
704
}
705
706
private void removeFromDatabase(String key) {
707
// Database removal logic
708
}
709
}
710
711
// Provider class for SPI registration
712
public class CustomProvider extends Provider {
713
714
public CustomProvider() {
715
super(10, "2.25.1"); // Priority and version
716
717
// Register custom implementations
718
put("LoggerContextFactory", "com.example.CustomLoggerContextFactory");
719
put("ThreadContextMap", "com.example.DatabaseThreadContextMap");
720
}
721
722
@Override
723
public String getVersionStr() {
724
return "2.25.1";
725
}
726
}
727
```