Journalisation avec Retrofit 2


308

J'essaie d'obtenir le JSON exact qui est envoyé dans la demande. Voici mon code:

OkHttpClient client = new OkHttpClient();
client.interceptors().add(new Interceptor(){
   @Override public com.squareup.okhttp.Response intercept(Chain chain) throws IOException {
      Request request = chain.request();
      Log.e(String.format("\nrequest:\n%s\nheaders:\n%s",
                          request.body().toString(), request.headers()));
      com.squareup.okhttp.Response response = chain.proceed(request);
      return response;
   }
});
Retrofit retrofit = new Retrofit.Builder()
   .baseUrl(API_URL)
   .addConverterFactory(GsonConverterFactory.create())
   .client(client).build();

Mais je ne vois cela que dans les journaux:

request:
com.squareup.okhttp.RequestBody$1@3ff4074d
headers:
Content-Type: application/vnd.ll.event.list+json

Comment suis-je censé effectuer une journalisation appropriée, compte tenu de la suppression de setLog()et setLogLevel()que nous utilisions avec Retrofit 1?

Réponses:


699

Dans Retrofit 2, vous devez utiliser HttpLoggingInterceptor .

Ajoutez une dépendance à build.gradle. La dernière version en date d'octobre 2019 est:

implementation 'com.squareup.okhttp3:logging-interceptor:4.2.1'

Créez un Retrofitobjet comme celui-ci:

HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("https://backend.example.com")
        .client(client)
        .addConverterFactory(GsonConverterFactory.create())
        .build();

return retrofit.create(ApiClient.class);

En cas d'avertissement de dépréciation, modifiez simplement setLevel:

interceptor.level(HttpLoggingInterceptor.Level.BODY);

La solution ci-dessus vous donne des messages logcat très similaires aux anciens définis par

setLogLevel(RestAdapter.LogLevel.FULL)

En cas dejava.lang.ClassNotFoundException :

La version plus ancienne de Retrofit peut nécessiter une logging-interceptorversion plus ancienne . Jetez un œil aux sections commentaires pour plus de détails.


30
Cela a été ajouté à OkHttp seulement 15 jours après ma question, c'est bien que les besoins de la communauté aient un impact si rapide!
Gabor

1
et vous n'avez plus besoin d'ajouter le référentiel d'instantanés de sonatype à votre build.gradle
James Goodwin

13
retrofit 2.1.0 utilise cette compilation 'com.squareup.okhttp3: logging-interceptor: 3.3.1'
jayellos

2
@jayellos Merci de l'avoir signalé. Si vous utilisez une version inférieure à 3.3.1, vous n'obtiendrez aucune exception de méthode.
guydemossyrock

2
Je reçois: Vous n'avez trouvé aucune idée de la classe "okhttp3.internal.Platform"?
corlaez

35

J'ai rencontré le truc comme vous et j'ai essayé de demander à l'auteur du livre Retrofit: Love working with APIs on Android (here is the link ) (nope! Je ne fais pas de pub pour eux .... mais ils sont vraiment sympas les gars :) Et l'auteur m'a répondu très bientôt, avec à la fois la méthode Log sur Retrofit 1.9 et Retrofit 2.0-beta.

Et voici le code de Retrofit 2.0-beta:

HttpLoggingInterceptor logging = new HttpLoggingInterceptor();  
// set your desired log level
logging.setLevel(Level.BODY);

OkHttpClient httpClient = new OkHttpClient();  
// add your other interceptors …

// add logging as last interceptor
httpClient.interceptors().add(logging);  // <-- this is the important line!

Retrofit retrofit = new Retrofit.Builder()  
   .baseUrl(API_BASE_URL)
   .addConverterFactory(GsonConverterFactory.create())
   .client(httpClient)
   .build();

Voici comment ajouter une méthode de journalisation à l'aide de HttpLoggingInterceptor . De plus, si vous êtes le lecteur de ce livre que j'ai mentionné ci-dessus, vous constaterez peut-être qu'il dit qu'il n'y a plus de méthode de journalisation avec Retrofit 2.0 - ce qui, j'avais demandé à l'auteur, n'est pas correct et ils mettront à jour le livre l'année prochaine en parlant à propos de ça.

// Si vous n'êtes pas familier avec la méthode Log dans Retrofit, je voudrais partager quelque chose de plus.

Il convient également de noter qu'il existe certains niveaux de journalisation que vous pouvez sélectionner. J'utilise le Level.BODY la plupart du temps, ce qui donnera quelque chose comme ça:

entrez la description de l'image ici

Vous pouvez trouver presque tout le personnel http à l'intérieur de l'image: l'en-tête, le contenu et la réponse, etc.

Et parfois, vous n'avez vraiment pas besoin de tous les invités pour assister à votre fête: je veux juste savoir s'il est correctement connecté, cet appel Internet est passé avec succès dans mon Activiy & Fragmetn. Ensuite, vous êtes libre d'utiliser Level.BASIC , qui retournera quelque chose comme ceci:

entrez la description de l'image ici

Pouvez-vous trouver le code d'état 200 OK à l' intérieur? C'est ça :)

Il y en a aussi un autre, Level.HEADERS , qui ne renverra que l'en-tête du réseau. Ya bien sûr une autre photo ici:

entrez la description de l'image ici

C'est tout l'astuce Logging;)

Et je voudrais vous partager le tutoriel que j'ai beaucoup appris là-bas . Ils ont un tas d'excellents articles sur presque tout ce qui concerne Retrofit, et ils continuent de mettre à jour l'article, en même temps que Retrofit 2.0 arrive. Veuillez jeter un œil à ces travaux qui, je pense, vous feront gagner beaucoup de temps.


D'où provient HttpLoggingInterceptor?
Radu

5
De plus, cela ne fonctionne pas pour moi. httpClient.interceptors.add veut un com.squareup.okhttp.Interceptor et non un okhttp3.logging.HttpLoggingInterceptor
Radu

@Radu Sur quelle version de Retrofit êtes-vous? Votre gradle devrait avoir quelque chose comme ceci: compilez 'com.squareup.retrofit2: retrofit: 2.0.0-beta4'
peitek

Cela ne fonctionnerait pas pour la journalisation des en-têtes de demande car il est nécessaire d'ajouter Interceptor comme un intercepteur réseau! Voir: stackoverflow.com/a/36847027/2557258
Yazon2006

12

Voici un Interceptorqui enregistre à la fois les corps de demande et de réponse (en utilisant Timber, basé sur un exemple des documents OkHttp et d'autres réponses SO):

public class TimberLoggingInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();

        long t1 = System.nanoTime();
        Timber.i("Sending request %s on %s%n%s", request.url(), chain.connection(), request.headers());
        Timber.v("REQUEST BODY BEGIN\n%s\nREQUEST BODY END", bodyToString(request));

        Response response = chain.proceed(request);

        ResponseBody responseBody = response.body();
        String responseBodyString = response.body().string();

        // now we have extracted the response body but in the process
        // we have consumed the original reponse and can't read it again
        // so we need to build a new one to return from this method

        Response newResponse = response.newBuilder().body(ResponseBody.create(responseBody.contentType(), responseBodyString.getBytes())).build();

        long t2 = System.nanoTime();
        Timber.i("Received response for %s in %.1fms%n%s", response.request().url(), (t2 - t1) / 1e6d, response.headers());
        Timber.v("RESPONSE BODY BEGIN:\n%s\nRESPONSE BODY END", responseBodyString);

        return newResponse;
    }

    private static String bodyToString(final Request request){

        try {
            final Request copy = request.newBuilder().build();
            final Buffer buffer = new Buffer();
            copy.body().writeTo(buffer);
            return buffer.readUtf8();
        } catch (final IOException e) {
            return "did not work";
        }
    }
}

6

Essaye ça:

Request request = chain.request();
Buffer buffer = new Buffer();
request.body().writeTo(buffer);
String body = buffer.readUtf8();

Après cela, bodyil y a le JSON qui vous intéresse.


1
comment imprimer la réponse du corps?
Gilberto Ibarra du

3
@GilbertoIbarra use String bodyString = response.body().string(); log(bodyString); response = response.newBuilder().body(ResponseBody.create(response.body().contentType(), bodyString)).build(); (Vous ne pouvez pas lire le corps de la réponse plus d'une fois, vous devrez donc créer une nouvelle réponse avec le constructeur)
Anton Ryabyh

C'est une béquille dont nous n'avons plus besoin. Voici un exemple de journalisation de travail: stackoverflow.com/a/36847027/2557258
Yazon2006

6

Le problème principal auquel j'ai été confronté était l'ajout dynamique d'en-têtes et leur journalisation dans le débogage logcat. J'ai essayé d'ajouter deux intercepteurs. Un pour la journalisation et un pour ajouter des en-têtes sur le pouce (autorisation de jeton). Le problème était que nous pouvons .addInterceptor ou .addNetworkInterceptor. Comme Jake Wharton me l'a dit: "Les intercepteurs réseau viennent toujours après les intercepteurs d'application. Voir https://github.com/square/okhttp/wiki/Interceptors ". Voici donc un exemple de travail avec les en-têtes et les journaux:

OkHttpClient httpClient = new OkHttpClient.Builder()
            //here we can add Interceptor for dynamical adding headers
            .addNetworkInterceptor(new Interceptor() {
                @Override
                public Response intercept(Chain chain) throws IOException {
                    Request request = chain.request().newBuilder().addHeader("test", "test").build();
                    return chain.proceed(request);
                }
            })
            //here we adding Interceptor for full level logging
            .addNetworkInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
            .build();

    Retrofit retrofit = new Retrofit.Builder()
            .addConverterFactory(GsonConverterFactory.create(gsonBuilder.create()))
            .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
            .client(httpClient)
            .baseUrl(AppConstants.SERVER_ADDRESS)
            .build();

5

Je ne sais pas si setLogLevel () reviendra dans la version finale 2.0 de Retrofit mais pour l'instant vous pouvez utiliser un intercepteur pour la journalisation.

Un bon exemple peut être trouvé dans le wiki OkHttp: https://github.com/square/okhttp/wiki/Interceptors

OkHttpClient client = new OkHttpClient();
client.interceptors().add(new LoggingInterceptor());

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("http://www.yourjsonapi.com")
        .addConverterFactory(GsonConverterFactory.create())
        .client(client)
        .build();

soit vous n'avez pas lu ma question, soit vous n'avez pas lu la page vers laquelle vous créez un lien ... car LoggingInterceptor n'essaie pas de consigner le corps de la demande (et ne résout donc pas mon problème)
Gabor


5

Si vous utilisez Retrofit2 et okhttp3, vous devez savoir qu'Interceptor fonctionne par file d'attente. Ajoutez donc loggingInterceptor à la fin, après vos autres intercepteurs:

HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
        if (BuildConfig.DEBUG)
            loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);

 new OkHttpClient.Builder()
                .connectTimeout(60, TimeUnit.SECONDS)
                .readTimeout(60, TimeUnit.SECONDS)
                .writeTimeout(60, TimeUnit.SECONDS)
                .addInterceptor(new CatalogInterceptor(context))//first
                .addInterceptor(new OAuthInterceptor(context))//second
                .authenticator(new BearerTokenAuthenticator(context))
                .addInterceptor(loggingInterceptor)//third, log at the end
                .build();

4

Pour ceux qui ont besoin d'une journalisation de haut niveau dans Retrofit, utilisez l'intercepteur comme celui-ci

public static class LoggingInterceptor implements Interceptor {
    @Override public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        long t1 = System.nanoTime();
        String requestLog = String.format("Sending request %s on %s%n%s",
                request.url(), chain.connection(), request.headers());
        //YLog.d(String.format("Sending request %s on %s%n%s",
        //        request.url(), chain.connection(), request.headers()));
        if(request.method().compareToIgnoreCase("post")==0){
            requestLog ="\n"+requestLog+"\n"+bodyToString(request);
        }
        Log.d("TAG","request"+"\n"+requestLog);

        Response response = chain.proceed(request);
        long t2 = System.nanoTime();

        String responseLog = String.format("Received response for %s in %.1fms%n%s",
                response.request().url(), (t2 - t1) / 1e6d, response.headers());

        String bodyString = response.body().string();

        Log.d("TAG","response"+"\n"+responseLog+"\n"+bodyString);

        return response.newBuilder()
                .body(ResponseBody.create(response.body().contentType(), bodyString))
                .build();
        //return response;
    }
}

public static String bodyToString(final Request request) {
    try {
        final Request copy = request.newBuilder().build();
        final Buffer buffer = new Buffer();
        copy.body().writeTo(buffer);
        return buffer.readUtf8();
    } catch (final IOException e) {
        return "did not work";
    }
}`

Courtoisie : https://github.com/square/retrofit/issues/1072#


vous auriez dû mentionner que vous avez copié depuis github.com/square/retrofit/issues/1072# (pour donner du crédit à votre source)
Gabor

C'est une béquille dont nous n'avons plus besoin. Voici un exemple de journalisation de travail: stackoverflow.com/a/36847027/2557258
Yazon2006

4

Code Kotlin

        val interceptor = HttpLoggingInterceptor()
        interceptor.level = HttpLoggingInterceptor.Level.BODY
        val client = OkHttpClient.Builder().addInterceptor(interceptor).build()
        val retrofit = Retrofit.Builder()
                .baseUrl(BASE_URL)
                .client(client)
                .addConverterFactory(GsonConverterFactory.create())
                .build()

        return retrofit.create(PointApi::class.java)

2

Vous pouvez également ajouter le Stetho de Facebook et consulter les traces du réseau dans Chrome: http://facebook.github.io/stetho/

final OkHttpClient.Builder builder = new OkHttpClient.Builder();
if (BuildConfig.DEBUG) {
    builder.networkInterceptors().add(new StethoInterceptor());
}

Ouvrez ensuite "chrome: // inspecter" dans Chrome ...


2

cela créera un objet de modification avec Logging. sans créer d'objets séparés.

 private static final Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(BASE_URL)
            .client(new OkHttpClient().newBuilder()
                    .addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
                    .readTimeout(READ_TIMEOUT_SECONDS, TimeUnit.SECONDS)
                    .writeTimeout(WRITE_TIMEOUT_SECONDS, TimeUnit.SECONDS)
                    .connectTimeout(CONNECTION_TIMEOUT_SECONDS, TimeUnit.SECONDS)
                    .build())
            .addConverterFactory(GsonConverterFactory.create())
            .build();

2

Ajoutez d'abord la dépendance à build.gradle:

implémentation 'com.squareup.okhttp3: logging-interceptor: 3.12.1'

Lorsque vous utilisez Kotlin, vous pouvez ajouter Logging Interceptor comme ceci:

companion object {
    val okHttpClient = OkHttpClient().newBuilder()
            .addInterceptor(HttpLoggingInterceptor().apply {
                level = HttpLoggingInterceptor.Level.BODY
            })
            .build()


    fun getRetrofitInstance(): Retrofit {
        val retrofit = Retrofit.Builder()
                .client(okHttpClient)
                .baseUrl(ScanNShopConstants.BASE_URL)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .build()

        return retrofit
    }
}

2

L'ensemble de code suivant fonctionne sans aucun problème pour moi

Gradle

// Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.12.1'

RetrofitClient

HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);

OkHttpClient client = new OkHttpClient.Builder()
        .addInterceptor(logging)
        .build();

retrofit = new Retrofit.Builder()
        .baseUrl(BASE_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .client(client)
        .build();

On peut également vérifier les résultats en allant dans l'onglet Profiler en bas d'Android Studio, puis en cliquant sur le signe + pour démarrer une nouvelle session, puis sélectionnez le pic souhaité dans "Réseau". Là, vous pouvez tout obtenir, mais c'est lourd et lent. Veuillez voir l'image ci-dessous.

entrez la description de l'image ici


1

La meilleure façon de procéder correctement dans Retrofit 2 est d'ajouter l'intercepteur de l'enregistreur en tant qu'intercepteur réseau, cela imprimera également les en-têtes du réseau et vos en-têtes personnalisés. L'important est de se rappeler que l'intercepteur fonctionne comme une pile et assurez-vous d'ajouter le logger à la fin de tout.

OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.addInterceptor(new MyCustomInterceptor());
builder.connectTimeout(60, TimeUnit.SECONDS);
builder.readTimeout(60, TimeUnit.SECONDS);
builder.writeTimeout(60, TimeUnit.SECONDS);
// important line here
builder.addNetworkInterceptor(LoggerInterceptor());

1

La plupart des réponses ici couvrent presque tout sauf cet outil, l'une des façons les plus cool de voir le journal.

C'est Stetho de Facebook . C'est le superbe outil pour surveiller / enregistrer le trafic réseau de votre application sur Google Chrome . Vous pouvez également trouver ici sur Github.

entrez la description de l'image ici


1

pour Retrofit 2.0.2, le code est comme

   **HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
        logging.setLevel(HttpLoggingInterceptor.Level.BODY);
        OkHttpClient.Builder httpClient=new OkHttpClient.Builder();
        httpClient.addInterceptor(logging);**


        if (retrofit == null) {
            retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    **.client(httpClient.build())**
                    .build();
        }

1

Voici un moyen simple de filtrer tous les paramètres de demande / réponse des journaux à l'aide de HttpLoggingInterceptor:

// Request patterns to filter
private static final String[] REQUEST_PATTERNS = {
    "Content-Type",
};
// Response patterns to filter
private static final String[] RESPONSE_PATTERNS = {"Server", "server", "X-Powered-By", "Set-Cookie", "Expires", "Cache-Control", "Pragma", "Content-Length", "access-control-allow-origin"};

// Log requests and response
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
    @Override
    public void log(String message) {

        // Blacklist the elements not required
        for (String pattern: REQUEST_PATTERNS) {
            if (message.startsWith(pattern)) {
                return;
            }
        }
        // Any response patterns as well...
        for (String pattern: RESPONSE_PATTERNS) {
            if (message.startsWith(pattern)) {
                return;
            }
        }
        Log.d("RETROFIT", message);
    }
});
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

Voici l'essentiel:

https://gist.github.com/mankum93/179c2d5378f27e95742c3f2434de7168


1

J'étais également coincé dans une situation similaire, la setLevel()méthode ne venait pas, quand j'essayais de l'appeler avec l'instance de HttpLoggingInterceptor, comme ceci:

HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

Voici comment je l'ai résolu, pour générer un journal pour Retrofit2,

Je suppose que vous avez ajouté la dépendance,

implementation "com.squareup.okhttp3:logging-interceptor:4.7.2"

Pour la dernière version que vous pouvez consulter, ce lien:

https://github.com/square/okhttp/tree/master/okhttp-logging-interceptor )

Ici, ils ont également expliqué comment ajouter.

J'ai créé une classe avec un nom AddLoggingInterceptor, voici mon code,

public class AddLoggingInterceptor {

    public static OkHttpClient setLogging(){
        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

        OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .addInterceptor(loggingInterceptor)
                .build();

        return okHttpClient;
    }
}

Ensuite, où nous instancions notre rénovation,

 public static Retrofit getRetrofitInstance() {
    if (retrofit == null) {
        retrofit = new retrofit2.Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .client(AddLoggingInterceptor.setLogging()) // here the method is called inside client() method, with the name of class, since it is a static method.
                .build();
    }
    return retrofit;
}

Maintenant, vous pouvez voir le journal généré dans votre Android Studio, vous devrez peut-être rechercher le okHttpprocessus de filtrage. Ça a marché pour moi. Si vous rencontrez des problèmes, vous pouvez m'envoyer un SMS ici.


0

J'ai trouvé le moyen d'imprimer

OkHttpClient okHttpClient = new OkHttpClient.Builder()
            .addInterceptor(new Interceptor() {
                @Override
                public Response intercept(Chain chain) throws IOException {
                    Request request = chain.request();
                    if (BuildConfig.DEBUG) {
                        Log.e(getClass().getName(), request.method() + " " + request.url());
                        Log.e(getClass().getName(), "" + request.header("Cookie"));
                        RequestBody rb = request.body();
                        Buffer buffer = new Buffer();
                        if (rb != null)
                            rb.writeTo(buffer);
                        LogUtils.LOGE(getClass().getName(), "Payload- " + buffer.readUtf8());
                    }
                    return chain.proceed(request);
                }
            })
            .readTimeout(60, TimeUnit.SECONDS)
            .connectTimeout(60, TimeUnit.SECONDS)
            .build();

            iServices = new Retrofit.Builder()
                    .baseUrl("Your Base URL")
                    .client(okHttpClient)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build()
                    .create(Your Service Interface .class);

Travaille pour moi.


0

L'intercepteur de Retrofit est une excellente fonctionnalité qui vous permet de travailler avec des requêtes http. Il existe deux types d'entre eux: les intercepteurs d'application et de réseau.

Je recommanderais d'utiliser Charles Web Debugging Proxy Applicationsi vous avez besoin de consigner vos demandes / réponses. La sortie est très similaire à Stetho mais c'est un instrument plus puissant que vous n'avez pas besoin d'ajouter comme dépendance à une application


-11

Hé les gars, je trouve déjà une solution:

  public static <T> T createApi(Context context, Class<T> clazz, String host, boolean debug) {
    if (singleton == null) {
        synchronized (RetrofitUtils.class) {
            if (singleton == null) {
                RestAdapter.Builder builder = new RestAdapter.Builder();
                builder
                        .setEndpoint(host)
                        .setClient(new OkClient(OkHttpUtils.getInstance(context)))
                        .setRequestInterceptor(RequestIntercepts.newInstance())
                        .setConverter(new GsonConverter(GsonUtils.newInstance()))
                        .setErrorHandler(new ErrorHandlers())
                        .setLogLevel(debug ? RestAdapter.LogLevel.FULL : RestAdapter.LogLevel.NONE)/*LogLevel.BASIC will cause response.getBody().in() close*/
                        .setLog(new RestAdapter.Log() {
                            @Override
                            public void log(String message) {
                                if (message.startsWith("{") || message.startsWith("["))
                                    Logger.json(message);
                                else {
                                    Logger.i(message);
                                }
                            }
                        });
                singleton = builder.build();
            }
        }
    }
    return singleton.create(clazz);
}

Le rappel de setLog peut entrer chaque journal
Vihuela Yao

1
ceci utilise la mise à niveau v1, pas v2
Gabor
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.