aws-amplify / aws-sdk-android

AWS SDK for Android. For more information, see our web site:
https://docs.amplify.aws
Other
1.02k stars 548 forks source link

How to attach a custom logger to the AmazonS3Client #2463

Open SailReal opened 3 years ago

SailReal commented 3 years ago

State your question

How can I attach a custom logger e.g. Timber to the internal logging mechanism of aws-android-sdk-s3?

AmazonS3Client is using a LogFactory which itself is instantiating AndroidLog and is logging to android.util.Log. I'm using Timber and would love to persist some of the queries for better troubleshooting with users but I'm unable to attach it to this logging chain?

Which AWS Services are you utilizing?

aws-android-sdk-s3

Provide code snippets (if applicable)

Have already tried a lot of different things, among others

LogFactory.setLevel(LogFactory.Level.ALL);
Logger.getLogger(AmazonS3Client.class.getCanonicalName()).setLevel(Level.ALL);
Logger.getLogger(AmazonS3Client.class.getCanonicalName()).addHandler(new Handler() {
    @Override
    public void publish(LogRecord record) {
        Timber.tag("S3Client").d(record.getMessage());
    }
    ...
});

or

LogManager.getLogManager().getLogger("com.amazonaws.request").addHandler(new Handler() {
            @Override
            public void publish(LogRecord record) {
                Timber.tag("S3Client").d(record.getMessage());
            }
            ...
});

Have also tried in the discord channel, there I was asked to create an issue on github.

Environment(please complete the following information):

Device Information (please complete the following information):

robrechta commented 2 years ago

I did some hacky trick, I have to admit, it is dirty, but I wanted to see if it was possible. I've used reflection. Somewhere in an init block in my code (kotlin) I did the following:

init {
        val field = LogFactory::class.java.getDeclaredField("logMap")
        field.isAccessible = true

        val oldMap = field.get(null) as HashMap<*, *>;
        val newMap : HashMap<String, com.amazonaws.logging.Log> = object : HashMap<String, com.amazonaws.logging.Log>() {
            init {
                // those are the following keys that we know that are used in the aws library. When setting this upfront,
                // the LogFactory.getLog will return these instead of the build-in logger
                val keys = arrayOf(
                    "AmazonWebServiceClient",
                    "VersionInfoUtils",
                    "UrlHttpClient",
                    "com.amazonaws.request",
                    "AmazonHttpClient",
                    "InternalConfig",
                    "AWSIotMqttManager",
                    "AWS4Signer",
                )
                keys.forEach { super.put(it, MyCustomLogger(... )) }
            }
            override fun put(key: String, value: com.amazonaws.logging.Log): com.amazonaws.logging.Log? {
                Log.w("LOG-OVERRIDE", "following key is not set, it is possible that the aws.AndroidLogger is still used: $key")
                return super.put(key, MyCustomLogger(....))
            }
        }

        field.set(null, newMap)
}

One downside of this approach, is that you have to know which 'logger' is used in the aws-sdk, because when there is no logger found in the logMap. The first time a logger is requested by the LoggerFactory, the logger that is created in the LoggerFactory will be returned, and not the MyCustomLogger. To know which logger is used, I added that Log.w statement with that specific tag, so I could easily filter in the logs which keys I should set upfront.

I have to warn, this is very implementation specific, and will possible break when upgrading the aws library. So be carefull when you use this in production.

Therefore I also like to see a possibility to set a custom logger.

Edit, with MyCustomLogger I had the possibility to add additional info to the log-statements, and to play around with the logLevels.