Files
oops/oppgavetekster/oving6/Logger.md
Andreas Omholt Olsen 57adabe5be Add oving 6
2026-02-23 12:10:01 +01:00

8.2 KiB

Delegering - Logger-oppgave

Denne oppgaven bruker delegeringsteknikken for å implementere en fleksibel måte å håndtere logging (av feil i programmer) på.

Logging

Ved kjøring av programmer er det ofte behov for å logge hva som skjer underveis, slik at det blir lettere å drive feilsøking i etterkant. F.eks. kan en lagre feilmeldinger til fil, med tidspunkt og litt om programtilstanden og hvis programmet kræsjer ordentlig, så kan brukeren sende logg-fila som e-post til utviklerne. En enkel måte å støtte logging på er å lage en hjelpeklasse med én metode, f.eks. log(String melding), og så er det hjelpeklassen som bestemmer om meldingen skal vises i statuslinja, skrives til fil, sendes som melding til en alarmsentral osv. Hjelpeklassen kan kanskje brukes av mange programmer, og siden behovene vil variere er det viktig å gjøre dette fleksibelt. Denne oppgaven bruker grensesnitt og delegeringsteknikken for å implementere fleksibel logging, litt på samme måte som eksisterende loggingsrammeverk (se f.eks. Java sin egen loggingsfunksjonalitet, Apache sitt log4j-rammeverk, eller Googles "Java logging framework").

Alle filene i denne oppgaven skal lages i oving6/logger.

ILogger-grensesnittet

Logging gjøres ved å bruke ulike implementasjoner av ILogger, som er definert som følger:

package oving6.logger;

public interface ILogger {

    String ERROR = "error";
    String INFO = "info";
    String WARNING = "warning";

    void log(String severity, String message, Exception exception);
}

ILogger-grensesnittet definerer én log-metode som brukes til all logging:

  • severity-argumentet angir alvorlighetsgraden, og må være en av String-verdiene ERROR, WARNING eller INFO, som er definert som konstanter i grensesnittet.
  • message-argumentet er en melding om hva som var feil.
  • exception-argumentet er et unntaksobjekt, som kan gi mer informasjon av hva som var feil, men kan være null.

En typisk bruk vil være i catch-delen av en try/catch:

ILogger logger = ...
...
try {
    ...
} catch (IOException ioe) {
    logger.log(ILogger.ERROR, "Feil ved lesing fra fil", ioe);
}

Akkurat hvordan logging utføres bestemmes av hvilken implementasjon av ILogger-grensesnittet en bruker, og i denne oppgaven skal du implementere følgende tre klasser:

  • DistributingLogger - delegerer til andre loggere basert på alvorlighetsgraden.
  • FilteringLogger - delegerer til en annen logger, men kun for spesifikke alvorlighetsgrader.
  • StreamLogger - skriver logg-meldingen til en angitt strøm.

Hver av disse utgjør én av deloppgavene beskrevet under.

Del 1 - StreamLogger

En StreamLogger sørger for å skrive alle logg-meldinger til en angitt OutputStream, med én melding pr. linje (altså linjeskift mellom hver melding). OutputStream-objektet må gis inn i konstruktøren:

  • StreamLogger(OutputStream stream) - initialiserer StreamLogger-objektet slik at logg-meldinger skrives til stream.

Eksempel på bruk:

ILogger logger = new StreamLogger(System.out);
logger.log(ILogger.INFO, "Denne meldingen er til informasjon og skrives til System.out", null);

Husk å kalle flush-metoden til OutputStream etter at logg-meldingen er skrevet.

Det skal også være mulig å angi en såkalt format-string, dvs. en String som fungerer som en slags mal for hva som skrives, f.eks. "%s: %s (%s)":

  • void setFormatString(String formatString) - setter format-string-en som brukes for å lage logg-meldingen som skrives. Det settes ingen andre krav til validering av formatString-argumentet annet enn at det ikke kan være null.

Effekten av skriving skal være som om man ga format-string-en som første argument til String.format-metoden etterfulgt av severity-, message- og exception-argumentene, og så skrev ut det denne metoden returnerer:

String logMessage = String.format(formatString, severity, message, exception);
// skriv logMessage til OutputStream-en her

Merk at dersom format-string-en ikke er satt, så skal den ha en fornuftig start-verdi.

Testkode for oppgaven: oving6/logger/StreamLoggerTest.java.

Del 2 - FilteringLogger

FilteringLogger-klassen implementerer ILogger-grensesnittet og delegerer til en annen ILogger-implementasjon, men bare hvis alvorlighetsgraden er en av et sett angitte verdier. Både loggeren det delegeres til og alvorlighetsgradene angis når FilteringLogger-objektet opprettes:

  • FilteringLogger(ILogger logger, String... severities) - initialiserer FilteringLogger-objektet så det delegerer logging til logger-argumentet, men bare hvis alvorlighetsgraden som gis til log-metoden er en av verdiene angitt i severities-argumentet. severities-argumentet er et såkalt varargs-argument, som du kan lese mer om her: Varargs - variabelt antall argumenter. Det viktigste å vite her er at det du får inn i metoden din vil være en variabel severities som er av typen string array (String[]). Du kan hente ut elementer her via severities[0], sjekke lengde ved severities.length og ellers bruke alle normale arraymetoder.

Det skal også være mulig å sjekke om logging er på og slå logging av og på i etterkant:

  • boolean isLogging(String severity) - returnerer true hvis logging er slått på for den angitte alvorlighetsgraden og false ellers.
  • void setIsLogging(String severity, boolean value) - slår logging på (value er true) eller av (value er false) for den angitte alvorlighetsgraden.

Eksempel på bruk:

ILogger syserrLogger = new StreamLogger(System.err);
FilteringLogger logger = new FilteringLogger(syserrLogger, ILogger.ERROR);

logger.log(ILogger.ERROR, "Denne meldingen er alvorlig og skrives til System.err", null);
logger.log(ILogger.WARNING, "Denne meldingen er en advarsel og blir filtrert bort", null);
logger.log(ILogger.INFO, "Denne meldingen er til informasjon og blir filtrert bort", null);

logger.setIsLogging(ILogger.WARNING, true);
logger.log(ILogger.WARNING, "Denne meldingen er en advarsel og blir nå skrevet til System.err", null);

Testkode for oppgaven: oving6/logger/FilteringLoggerTest.java.

Del 3 - DistributingLogger

DistributingLogger-klassen brukes for å fordele logg-meldinger til en av tre andre loggere, avhengig av alvorlighetsgraden til en logg-melding. Den har én hjelpe-logger for meldinger med alvorlighetsgrad ERROR, én for meldinger av alvorlighetsgrad WARNING og en for meldinger av alvorlighetsgrad INFO. Alle disse angis til konstruktøren:

  • DistributingLogger(ILogger errorLogger, ILogger warningLogger, ILogger infoLogger) - initialiserer objektet slik at den første loggeren brukes til alvorlighetsgraden ERROR, den andre til alvorlighetsgraden WARNING og den tredje til alvorlighetsgraden INFO.

I tillegg skal klassen ha en metode for å sette hver av dem individuelt:

  • void setLogger(String severity, ILogger logger) - setter/endrer loggeren som brukes for den angitte alvorlighetsgraden.

Eksempel på bruk:

ILogger syserrLogger = new StreamLogger(System.err);
ILogger sysoutLogger = new StreamLogger(System.out);
DistributingLogger logger = new DistributingLogger(syserrLogger, syserrLogger, sysoutLogger);

logger.log(ILogger.ERROR, "Denne meldingen er alvorlig og skrives til System.err", null);
logger.log(ILogger.WARNING, "Denne meldingen er en advarsel og skrives til System.err", null);
logger.log(ILogger.INFO, "Denne meldingen er til informasjon og skrives til System.out", null);

logger.setLogger(ILogger.WARNING, sysoutLogger);
logger.log(ILogger.WARNING, "Denne meldingen er en advarsel, men nå skrives den til System.out", null);

Testkode for oppgaven: oving6/logger/DistributingLoggerTest.java.