Add the user-visible logging interface.

Details:
- Add the logging level type WGPULoggingType, including levels verbose,
info, warning, and error,
- Add the API SetLoggingCallback, which bind the callback to deal with
logging string,
- Add the return command DeviceLoggingCallback and related code,
- Add DeviceBase::EmitLog(WGPULoggingType, const char*) , and
DeviceBase::EmitLog(const char*) use WGPULoggingType_info as default,
to post logging from native or server device to bound callback
via CallbackTaskManager.

BUG: dawn:792
Change-Id: I107b9134ff8567a46fa452509799e10b6862b8d3
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/52200
Reviewed-by: Austin Eng <enga@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Zhaoming Jiang <zhaoming.jiang@intel.com>
diff --git a/dawn.json b/dawn.json
index 7029aad..be6bf53 100644
--- a/dawn.json
+++ b/dawn.json
@@ -824,6 +824,13 @@
                 ]
             },
             {
+                "name": "set logging callback",
+                "args": [
+                    {"name": "callback", "type": "logging callback"},
+                    {"name": "userdata", "type": "void", "annotation": "*"}
+                ]
+            },
+            {
                 "name": "set device lost callback",
                 "args": [
                     {"name": "callback", "type": "device lost callback"},
@@ -876,6 +883,14 @@
             {"name": "userdata", "type": "void", "annotation": "*"}
         ]
     },
+    "logging callback": {
+        "category": "callback",
+        "args": [
+            {"name": "type", "type": "logging type"},
+            {"name": "message", "type": "char", "annotation": "const*"},
+            {"name": "userdata", "type": "void", "annotation": "*"}
+        ]
+    },
     "error filter": {
         "category": "enum",
         "values": [
@@ -894,6 +909,15 @@
             {"value": 4, "name": "device lost"}
         ]
     },
+    "logging type": {
+        "category": "enum",
+        "values": [
+            {"value": 0, "name": "verbose"},
+            {"value": 1, "name": "info"},
+            {"value": 2, "name": "warning"},
+            {"value": 3, "name": "error"}
+        ]
+    },
     "extent 3D": {
         "category": "structure",
         "members": [
diff --git a/dawn_wire.json b/dawn_wire.json
index 18f68d8..04dd836 100644
--- a/dawn_wire.json
+++ b/dawn_wire.json
@@ -109,6 +109,11 @@
             { "name": "type", "type": "error type"},
             { "name": "message", "type": "char", "annotation": "const*", "length": "strlen" }
         ],
+        "device logging callback": [
+            { "name": "device", "type": "ObjectHandle", "handle_type": "device" },
+            { "name": "type", "type": "logging type"},
+            { "name": "message", "type": "char", "annotation": "const*", "length": "strlen" }
+        ],
         "device lost callback" : [
             { "name": "device", "type": "ObjectHandle", "handle_type": "device" },
             { "name": "message", "type": "char", "annotation": "const*", "length": "strlen" }
@@ -148,6 +153,7 @@
             "DevicePopErrorScope",
             "DeviceSetDeviceLostCallback",
             "DeviceSetUncapturedErrorCallback",
+            "DeviceSetLoggingCallback",
             "ShaderModuleGetCompilationInfo",
             "QueueOnSubmittedWorkDone",
             "QueueWriteBuffer",
diff --git a/src/dawn_native/Device.cpp b/src/dawn_native/Device.cpp
index 3285244..1efe91d 100644
--- a/src/dawn_native/Device.cpp
+++ b/src/dawn_native/Device.cpp
@@ -83,6 +83,45 @@
         size_t count = 0;
     };
 
+    namespace {
+        struct LoggingCallbackTask : CallbackTask {
+          public:
+            LoggingCallbackTask(wgpu::LoggingCallback loggingCallback,
+                                WGPULoggingType loggingType,
+                                const char* message,
+                                void* userdata)
+                : mCallback(loggingCallback),
+                  mLoggingType(loggingType),
+                  mMessage(message),
+                  mUserdata(userdata) {
+                // The parameter of constructor is the same as those of callback.
+                // Since the Finish() will be called in uncertain future in which time the message
+                // may already disposed, we must keep a local copy in the CallbackTask.
+            }
+
+            void Finish() override {
+                // Original direct call: mLoggingCallback(message, mLoggingUserdata)
+                // Do the same here, but with everything bound locally.
+                mCallback(mLoggingType, mMessage.c_str(), mUserdata);
+            }
+
+            void HandleShutDown() override {
+                // Do the logging anyway
+                mCallback(mLoggingType, mMessage.c_str(), mUserdata);
+            }
+
+            void HandleDeviceLoss() override {
+                mCallback(mLoggingType, mMessage.c_str(), mUserdata);
+            }
+
+          private:
+            wgpu::LoggingCallback mCallback;
+            WGPULoggingType mLoggingType;
+            std::string mMessage;
+            void* mUserdata;
+        };
+    }  // anonymous namespace
+
     // DeviceBase
 
     DeviceBase::DeviceBase(AdapterBase* adapter, const DeviceDescriptor* descriptor)
@@ -271,22 +310,6 @@
         }
     }
 
-    void DeviceBase::APIInjectError(wgpu::ErrorType type, const char* message) {
-        if (ConsumedError(ValidateErrorType(type))) {
-            return;
-        }
-
-        // This method should only be used to make error scope reject. For DeviceLost there is the
-        // LoseForTesting function that can be used instead.
-        if (type != wgpu::ErrorType::Validation && type != wgpu::ErrorType::OutOfMemory) {
-            HandleError(InternalErrorType::Validation,
-                        "Invalid injected error, must be Validation or OutOfMemory");
-            return;
-        }
-
-        HandleError(FromWGPUErrorType(type), message);
-    }
-
     void DeviceBase::ConsumeError(std::unique_ptr<ErrorData> error) {
         ASSERT(error != nullptr);
         std::ostringstream ss;
@@ -303,6 +326,11 @@
         mUncapturedErrorUserdata = userdata;
     }
 
+    void DeviceBase::APISetLoggingCallback(wgpu::LoggingCallback callback, void* userdata) {
+        mLoggingCallback = callback;
+        mLoggingUserdata = userdata;
+    }
+
     void DeviceBase::APISetDeviceLostCallback(wgpu::DeviceLostCallback callback, void* userdata) {
         mDeviceLostCallback = callback;
         mDeviceLostUserdata = userdata;
@@ -953,6 +981,36 @@
         }
     }
 
+    void DeviceBase::EmitLog(const char* message) {
+        this->EmitLog(WGPULoggingType_Info, message);
+    }
+
+    void DeviceBase::EmitLog(WGPULoggingType loggingType, const char* message) {
+        if (mLoggingCallback != nullptr) {
+            // Use the thread-safe CallbackTaskManager routine
+            std::unique_ptr<LoggingCallbackTask> callbackTask =
+                std::make_unique<LoggingCallbackTask>(mLoggingCallback, loggingType, message,
+                                                      mLoggingUserdata);
+            mCallbackTaskManager->AddCallbackTask(std::move(callbackTask));
+        }
+    }
+
+    void DeviceBase::APIInjectError(wgpu::ErrorType type, const char* message) {
+        if (ConsumedError(ValidateErrorType(type))) {
+            return;
+        }
+
+        // This method should only be used to make error scope reject. For DeviceLost there is the
+        // LoseForTesting function that can be used instead.
+        if (type != wgpu::ErrorType::Validation && type != wgpu::ErrorType::OutOfMemory) {
+            HandleError(InternalErrorType::Validation,
+                        "Invalid injected error, must be Validation or OutOfMemory");
+            return;
+        }
+
+        HandleError(FromWGPUErrorType(type), message);
+    }
+
     QueueBase* DeviceBase::GetQueue() const {
         return mQueue.Get();
     }
diff --git a/src/dawn_native/Device.h b/src/dawn_native/Device.h
index c16d01b..1cb711a 100644
--- a/src/dawn_native/Device.h
+++ b/src/dawn_native/Device.h
@@ -210,6 +210,7 @@
 
         void APISetDeviceLostCallback(wgpu::DeviceLostCallback callback, void* userdata);
         void APISetUncapturedErrorCallback(wgpu::ErrorCallback callback, void* userdata);
+        void APISetLoggingCallback(wgpu::LoggingCallback callback, void* userdata);
         void APIPushErrorScope(wgpu::ErrorFilter filter);
         bool APIPopErrorScope(wgpu::ErrorCallback callback, void* userdata);
 
@@ -262,6 +263,8 @@
         void IncrementLazyClearCountForTesting();
         size_t GetDeprecationWarningCountForTesting();
         void EmitDeprecationWarning(const char* warning);
+        void EmitLog(const char* message);
+        void EmitLog(WGPULoggingType loggingType, const char* message);
         void APILoseForTesting();
         QueueBase* GetQueue() const;
 
@@ -393,6 +396,9 @@
         wgpu::ErrorCallback mUncapturedErrorCallback = nullptr;
         void* mUncapturedErrorUserdata = nullptr;
 
+        wgpu::LoggingCallback mLoggingCallback = nullptr;
+        void* mLoggingUserdata = nullptr;
+
         wgpu::DeviceLostCallback mDeviceLostCallback = nullptr;
         void* mDeviceLostUserdata = nullptr;
 
diff --git a/src/dawn_wire/client/ClientDoers.cpp b/src/dawn_wire/client/ClientDoers.cpp
index 64db688..2e864a4 100644
--- a/src/dawn_wire/client/ClientDoers.cpp
+++ b/src/dawn_wire/client/ClientDoers.cpp
@@ -33,10 +33,25 @@
             default:
                 return false;
         }
+        if (device == nullptr) {
+            // The device might have been deleted or recreated so this isn't an error.
+            return true;
+        }
         device->HandleError(errorType, message);
         return true;
     }
 
+    bool Client::DoDeviceLoggingCallback(Device* device,
+                                         WGPULoggingType loggingType,
+                                         const char* message) {
+        if (device == nullptr) {
+            // The device might have been deleted or recreated so this isn't an error.
+            return true;
+        }
+        device->HandleLogging(loggingType, message);
+        return true;
+    }
+
     bool Client::DoDeviceLostCallback(Device* device, char const* message) {
         if (device == nullptr) {
             // The device might have been deleted or recreated so this isn't an error.
diff --git a/src/dawn_wire/client/Device.cpp b/src/dawn_wire/client/Device.cpp
index 3a9189e..adfccdf 100644
--- a/src/dawn_wire/client/Device.cpp
+++ b/src/dawn_wire/client/Device.cpp
@@ -76,6 +76,13 @@
         }
     }
 
+    void Device::HandleLogging(WGPULoggingType loggingType, const char* message) {
+        if (mLoggingCallback) {
+            // Since client always run in single thread, calling the callback directly is safe.
+            mLoggingCallback(loggingType, message, mLoggingUserdata);
+        }
+    }
+
     void Device::HandleDeviceLost(const char* message) {
         if (mDeviceLostCallback && !mDidRunLostCallback) {
             mDidRunLostCallback = true;
@@ -114,6 +121,11 @@
         mErrorUserdata = errorUserdata;
     }
 
+    void Device::SetLoggingCallback(WGPULoggingCallback callback, void* userdata) {
+        mLoggingCallback = callback;
+        mLoggingUserdata = userdata;
+    }
+
     void Device::SetDeviceLostCallback(WGPUDeviceLostCallback callback, void* userdata) {
         mDeviceLostCallback = callback;
         mDeviceLostUserdata = userdata;
diff --git a/src/dawn_wire/client/Device.h b/src/dawn_wire/client/Device.h
index c72200b..122c509 100644
--- a/src/dawn_wire/client/Device.h
+++ b/src/dawn_wire/client/Device.h
@@ -36,6 +36,7 @@
         ~Device();
 
         void SetUncapturedErrorCallback(WGPUErrorCallback errorCallback, void* errorUserdata);
+        void SetLoggingCallback(WGPULoggingCallback errorCallback, void* errorUserdata);
         void SetDeviceLostCallback(WGPUDeviceLostCallback errorCallback, void* errorUserdata);
         void InjectError(WGPUErrorType type, const char* message);
         void PushErrorScope(WGPUErrorFilter filter);
@@ -50,6 +51,7 @@
                                        void* userdata);
 
         void HandleError(WGPUErrorType errorType, const char* message);
+        void HandleLogging(WGPULoggingType loggingType, const char* message);
         void HandleDeviceLost(const char* message);
         bool OnPopErrorScopeCallback(uint64_t requestSerial,
                                      WGPUErrorType type,
@@ -89,9 +91,11 @@
 
         WGPUErrorCallback mErrorCallback = nullptr;
         WGPUDeviceLostCallback mDeviceLostCallback = nullptr;
+        WGPULoggingCallback mLoggingCallback = nullptr;
         bool mDidRunLostCallback = false;
         void* mErrorUserdata = nullptr;
         void* mDeviceLostUserdata = nullptr;
+        void* mLoggingUserdata = nullptr;
 
         Queue* mQueue = nullptr;
 
diff --git a/src/dawn_wire/server/Server.cpp b/src/dawn_wire/server/Server.cpp
index 24fbeda..7a504ef 100644
--- a/src/dawn_wire/server/Server.cpp
+++ b/src/dawn_wire/server/Server.cpp
@@ -133,6 +133,15 @@
                 info->server->OnUncapturedError(info->self, type, message);
             },
             data->info.get());
+        // Set callback to post warning and other infomation to client.
+        // Almost the same with UncapturedError.
+        mProcs.deviceSetLoggingCallback(
+            device,
+            [](WGPULoggingType type, const char* message, void* userdata) {
+                DeviceInfo* info = static_cast<DeviceInfo*>(userdata);
+                info->server->OnLogging(info->self, type, message);
+            },
+            data->info.get());
         mProcs.deviceSetDeviceLostCallback(
             device,
             [](const char* message, void* userdata) {
@@ -156,6 +165,7 @@
         // Un-set the error and lost callbacks since we cannot forward them
         // after the server has been destroyed.
         mProcs.deviceSetUncapturedErrorCallback(device, nullptr, nullptr);
+        mProcs.deviceSetLoggingCallback(device, nullptr, nullptr);
         mProcs.deviceSetDeviceLostCallback(device, nullptr, nullptr);
     }
 
diff --git a/src/dawn_wire/server/Server.h b/src/dawn_wire/server/Server.h
index 0ca756d..af16b88 100644
--- a/src/dawn_wire/server/Server.h
+++ b/src/dawn_wire/server/Server.h
@@ -200,6 +200,7 @@
         // Error callbacks
         void OnUncapturedError(ObjectHandle device, WGPUErrorType type, const char* message);
         void OnDeviceLost(ObjectHandle device, const char* message);
+        void OnLogging(ObjectHandle device, WGPULoggingType type, const char* message);
         void OnDevicePopErrorScope(WGPUErrorType type,
                                    const char* message,
                                    ErrorScopeUserdata* userdata);
diff --git a/src/dawn_wire/server/ServerDevice.cpp b/src/dawn_wire/server/ServerDevice.cpp
index 19e6298..939e632 100644
--- a/src/dawn_wire/server/ServerDevice.cpp
+++ b/src/dawn_wire/server/ServerDevice.cpp
@@ -67,6 +67,15 @@
         SerializeCommand(cmd);
     }
 
+    void Server::OnLogging(ObjectHandle device, WGPULoggingType type, const char* message) {
+        ReturnDeviceLoggingCallbackCmd cmd;
+        cmd.device = device;
+        cmd.type = type;
+        cmd.message = message;
+
+        SerializeCommand(cmd);
+    }
+
     bool Server::DoDevicePopErrorScope(ObjectId deviceId, uint64_t requestSerial) {
         auto* device = DeviceObjects().Get(deviceId);
         if (device == nullptr) {
diff --git a/src/tests/unittests/wire/WireCreatePipelineAsyncTests.cpp b/src/tests/unittests/wire/WireCreatePipelineAsyncTests.cpp
index a30b74c..d61a1d4 100644
--- a/src/tests/unittests/wire/WireCreatePipelineAsyncTests.cpp
+++ b/src/tests/unittests/wire/WireCreatePipelineAsyncTests.cpp
@@ -363,6 +363,9 @@
     EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(apiDevice, nullptr, nullptr))
         .Times(1)
         .InSequence(s1, s2);
+    EXPECT_CALL(api, OnDeviceSetLoggingCallback(apiDevice, nullptr, nullptr))
+        .Times(1)
+        .InSequence(s1, s2);
     EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(apiDevice, nullptr, nullptr))
         .Times(1)
         .InSequence(s1, s2);
diff --git a/src/tests/unittests/wire/WireDestroyObjectTests.cpp b/src/tests/unittests/wire/WireDestroyObjectTests.cpp
index 2c7ddc2..f613051 100644
--- a/src/tests/unittests/wire/WireDestroyObjectTests.cpp
+++ b/src/tests/unittests/wire/WireDestroyObjectTests.cpp
@@ -39,6 +39,9 @@
     EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(apiDevice, nullptr, nullptr))
         .Times(1)
         .InSequence(s1, s2);
+    EXPECT_CALL(api, OnDeviceSetLoggingCallback(apiDevice, nullptr, nullptr))
+        .Times(1)
+        .InSequence(s1, s2);
     EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(apiDevice, nullptr, nullptr))
         .Times(1)
         .InSequence(s1, s2);
@@ -94,6 +97,9 @@
         EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(apiDevice, nullptr, nullptr))
             .Times(1)
             .InSequence(s1, s2);
+        EXPECT_CALL(api, OnDeviceSetLoggingCallback(apiDevice, nullptr, nullptr))
+            .Times(1)
+            .InSequence(s1, s2);
         EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(apiDevice, nullptr, nullptr))
             .Times(1)
             .InSequence(s1, s2);
diff --git a/src/tests/unittests/wire/WireDisconnectTests.cpp b/src/tests/unittests/wire/WireDisconnectTests.cpp
index d3f65a9..81b75b2 100644
--- a/src/tests/unittests/wire/WireDisconnectTests.cpp
+++ b/src/tests/unittests/wire/WireDisconnectTests.cpp
@@ -152,6 +152,9 @@
     EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(apiDevice, nullptr, nullptr))
         .Times(1)
         .InSequence(s1, s2);
+    EXPECT_CALL(api, OnDeviceSetLoggingCallback(apiDevice, nullptr, nullptr))
+        .Times(1)
+        .InSequence(s1, s2);
     EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(apiDevice, nullptr, nullptr))
         .Times(1)
         .InSequence(s1, s2);
diff --git a/src/tests/unittests/wire/WireErrorCallbackTests.cpp b/src/tests/unittests/wire/WireErrorCallbackTests.cpp
index 0d136fd..fb4cad3 100644
--- a/src/tests/unittests/wire/WireErrorCallbackTests.cpp
+++ b/src/tests/unittests/wire/WireErrorCallbackTests.cpp
@@ -44,6 +44,16 @@
         mockDevicePopErrorScopeCallback->Call(type, message, userdata);
     }
 
+    class MockDeviceLoggingCallback {
+      public:
+        MOCK_METHOD(void, Call, (WGPULoggingType type, const char* message, void* userdata));
+    };
+
+    std::unique_ptr<StrictMock<MockDeviceLoggingCallback>> mockDeviceLoggingCallback;
+    void ToMockDeviceLoggingCallback(WGPULoggingType type, const char* message, void* userdata) {
+        mockDeviceLoggingCallback->Call(type, message, userdata);
+    }
+
     class MockDeviceLostCallback {
       public:
         MOCK_METHOD(void, Call, (const char* message, void* userdata));
@@ -66,6 +76,7 @@
         WireTest::SetUp();
 
         mockDeviceErrorCallback = std::make_unique<StrictMock<MockDeviceErrorCallback>>();
+        mockDeviceLoggingCallback = std::make_unique<StrictMock<MockDeviceLoggingCallback>>();
         mockDevicePopErrorScopeCallback =
             std::make_unique<StrictMock<MockDevicePopErrorScopeCallback>>();
         mockDeviceLostCallback = std::make_unique<StrictMock<MockDeviceLostCallback>>();
@@ -75,6 +86,7 @@
         WireTest::TearDown();
 
         mockDeviceErrorCallback = nullptr;
+        mockDeviceLoggingCallback = nullptr;
         mockDevicePopErrorScopeCallback = nullptr;
         mockDeviceLostCallback = nullptr;
     }
@@ -106,6 +118,23 @@
     FlushServer();
 }
 
+// Test the return wire for device user warning callbacks
+TEST_F(WireErrorCallbackTests, DeviceLoggingCallback) {
+    wgpuDeviceSetLoggingCallback(device, ToMockDeviceLoggingCallback, this);
+
+    // Setting the injected warning callback should stay on the client side and do nothing
+    FlushClient();
+
+    // Calling the callback on the server side will result in the callback being called on the
+    // client side
+    api.CallDeviceSetLoggingCallbackCallback(apiDevice, WGPULoggingType_Info, "Some message");
+
+    EXPECT_CALL(*mockDeviceLoggingCallback, Call(WGPULoggingType_Info, StrEq("Some message"), this))
+        .Times(1);
+
+    FlushServer();
+}
+
 // Test the return wire for error scopes.
 TEST_F(WireErrorCallbackTests, PushPopErrorScopeCallback) {
     wgpuDevicePushErrorScope(device, WGPUErrorFilter_Validation);
diff --git a/src/tests/unittests/wire/WireInjectDeviceTests.cpp b/src/tests/unittests/wire/WireInjectDeviceTests.cpp
index 28d7a20..21f9110 100644
--- a/src/tests/unittests/wire/WireInjectDeviceTests.cpp
+++ b/src/tests/unittests/wire/WireInjectDeviceTests.cpp
@@ -35,6 +35,7 @@
     WGPUDevice serverDevice = api.GetNewDevice();
     EXPECT_CALL(api, DeviceReference(serverDevice));
     EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice, _, _));
+    EXPECT_CALL(api, OnDeviceSetLoggingCallback(serverDevice, _, _));
     EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice, _, _));
     ASSERT_TRUE(
         GetWireServer()->InjectDevice(serverDevice, reservation.id, reservation.generation));
@@ -48,6 +49,7 @@
     // Called on shutdown.
     EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice, nullptr, nullptr))
         .Times(Exactly(1));
+    EXPECT_CALL(api, OnDeviceSetLoggingCallback(serverDevice, nullptr, nullptr)).Times(Exactly(1));
     EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice, nullptr, nullptr))
         .Times(Exactly(1));
 }
@@ -68,6 +70,7 @@
     WGPUDevice serverDevice = api.GetNewDevice();
     EXPECT_CALL(api, DeviceReference(serverDevice));
     EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice, _, _));
+    EXPECT_CALL(api, OnDeviceSetLoggingCallback(serverDevice, _, _));
     EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice, _, _));
     ASSERT_TRUE(
         GetWireServer()->InjectDevice(serverDevice, reservation.id, reservation.generation));
@@ -79,6 +82,7 @@
     // Called on shutdown.
     EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice, nullptr, nullptr))
         .Times(Exactly(1));
+    EXPECT_CALL(api, OnDeviceSetLoggingCallback(serverDevice, nullptr, nullptr)).Times(Exactly(1));
     EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice, nullptr, nullptr))
         .Times(Exactly(1));
 }
@@ -91,6 +95,7 @@
     WGPUDevice serverDevice = api.GetNewDevice();
     EXPECT_CALL(api, DeviceReference(serverDevice));
     EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice, _, _));
+    EXPECT_CALL(api, OnDeviceSetLoggingCallback(serverDevice, _, _));
     EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice, _, _));
     ASSERT_TRUE(
         GetWireServer()->InjectDevice(serverDevice, reservation.id, reservation.generation));
@@ -99,6 +104,7 @@
     wgpuDeviceRelease(reservation.device);
     EXPECT_CALL(api, DeviceRelease(serverDevice));
     EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice, nullptr, nullptr)).Times(1);
+    EXPECT_CALL(api, OnDeviceSetLoggingCallback(serverDevice, nullptr, nullptr)).Times(1);
     EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice, nullptr, nullptr)).Times(1);
     FlushClient();
 
@@ -124,6 +130,7 @@
     WGPUDevice serverDevice = api.GetNewDevice();
     EXPECT_CALL(api, DeviceReference(serverDevice));
     EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice, _, _));
+    EXPECT_CALL(api, OnDeviceSetLoggingCallback(serverDevice, _, _));
     EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice, _, _));
     ASSERT_TRUE(
         GetWireServer()->InjectDevice(serverDevice, reservation.id, reservation.generation));
@@ -137,6 +144,7 @@
     // Called on shutdown.
     EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice, nullptr, nullptr))
         .Times(Exactly(1));
+    EXPECT_CALL(api, OnDeviceSetLoggingCallback(serverDevice, nullptr, nullptr)).Times(Exactly(1));
     EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice, nullptr, nullptr))
         .Times(Exactly(1));
 }
@@ -152,6 +160,7 @@
     WGPUDevice serverDevice1 = api.GetNewDevice();
     EXPECT_CALL(api, DeviceReference(serverDevice1));
     EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice1, _, _));
+    EXPECT_CALL(api, OnDeviceSetLoggingCallback(serverDevice1, _, _));
     EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice1, _, _));
     ASSERT_TRUE(
         GetWireServer()->InjectDevice(serverDevice1, reservation1.id, reservation1.generation));
@@ -159,6 +168,7 @@
     WGPUDevice serverDevice2 = api.GetNewDevice();
     EXPECT_CALL(api, DeviceReference(serverDevice2));
     EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice2, _, _));
+    EXPECT_CALL(api, OnDeviceSetLoggingCallback(serverDevice2, _, _));
     EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice2, _, _));
     ASSERT_TRUE(
         GetWireServer()->InjectDevice(serverDevice2, reservation2.id, reservation2.generation));
@@ -171,6 +181,7 @@
     wgpuDeviceRelease(reservation1.device);
     EXPECT_CALL(api, DeviceRelease(serverDevice1));
     EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice1, nullptr, nullptr)).Times(1);
+    EXPECT_CALL(api, OnDeviceSetLoggingCallback(serverDevice1, nullptr, nullptr)).Times(1);
     EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice1, nullptr, nullptr)).Times(1);
     FlushClient();
 
@@ -180,6 +191,7 @@
 
     // Called on shutdown.
     EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice2, nullptr, nullptr)).Times(1);
+    EXPECT_CALL(api, OnDeviceSetLoggingCallback(serverDevice2, nullptr, nullptr)).Times(1);
     EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice2, nullptr, nullptr)).Times(1);
 }
 
@@ -193,6 +205,7 @@
     WGPUDevice serverDevice1 = api.GetNewDevice();
     EXPECT_CALL(api, DeviceReference(serverDevice1));
     EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice1, _, _));
+    EXPECT_CALL(api, OnDeviceSetLoggingCallback(serverDevice1, _, _));
     EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice1, _, _));
     ASSERT_TRUE(
         GetWireServer()->InjectDevice(serverDevice1, reservation1.id, reservation1.generation));
@@ -211,6 +224,7 @@
     WGPUDevice serverDevice2 = api.GetNewDevice();
     EXPECT_CALL(api, DeviceReference(serverDevice2));
     EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice2, _, _));
+    EXPECT_CALL(api, OnDeviceSetLoggingCallback(serverDevice2, _, _));
     EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice2, _, _));
     ASSERT_TRUE(
         GetWireServer()->InjectDevice(serverDevice2, reservation2.id, reservation2.generation));
@@ -224,8 +238,10 @@
 
     // Called on shutdown.
     EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice1, nullptr, nullptr)).Times(1);
+    EXPECT_CALL(api, OnDeviceSetLoggingCallback(serverDevice1, nullptr, nullptr)).Times(1);
     EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice1, nullptr, nullptr)).Times(1);
     EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice2, nullptr, nullptr)).Times(1);
+    EXPECT_CALL(api, OnDeviceSetLoggingCallback(serverDevice2, nullptr, nullptr)).Times(1);
     EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice2, nullptr, nullptr)).Times(1);
 }
 
diff --git a/src/tests/unittests/wire/WireTest.cpp b/src/tests/unittests/wire/WireTest.cpp
index bf08eaf..557bba4 100644
--- a/src/tests/unittests/wire/WireTest.cpp
+++ b/src/tests/unittests/wire/WireTest.cpp
@@ -43,6 +43,7 @@
 
     // This SetCallback call cannot be ignored because it is done as soon as we start the server
     EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(_, _, _)).Times(Exactly(1));
+    EXPECT_CALL(api, OnDeviceSetLoggingCallback(_, _, _)).Times(Exactly(1));
     EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(_, _, _)).Times(Exactly(1));
     SetupIgnoredCallExpectations();
 
@@ -95,6 +96,7 @@
         // called after the server is destroyed.
         EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(apiDevice, nullptr, nullptr))
             .Times(Exactly(1));
+        EXPECT_CALL(api, OnDeviceSetLoggingCallback(apiDevice, nullptr, nullptr)).Times(Exactly(1));
         EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(apiDevice, nullptr, nullptr))
             .Times(Exactly(1));
     }
@@ -135,6 +137,7 @@
         // called after the server is destroyed.
         EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(apiDevice, nullptr, nullptr))
             .Times(Exactly(1));
+        EXPECT_CALL(api, OnDeviceSetLoggingCallback(apiDevice, nullptr, nullptr)).Times(Exactly(1));
         EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(apiDevice, nullptr, nullptr))
             .Times(Exactly(1));
     }