diff --git a/ee/lib/gitlab/llm/chain/agents/zero_shot.rb b/ee/lib/gitlab/llm/chain/agents/zero_shot.rb
index 990ba30687709c875c2edcb773d1d9c2b6800c2b..65e9ab8ab46c8d6dd76f4bdf5f489f281c194afe 100644
--- a/ee/lib/gitlab/llm/chain/agents/zero_shot.rb
+++ b/ee/lib/gitlab/llm/chain/agents/zero_shot.rb
@@ -18,6 +18,7 @@ def initialize(user_input:, tools:, context:)
             @tools = tools
             @context = context
             @iterations = 0
+            @logger = Gitlab::Llm::Logger.build
           end
 
           PROMPT_TEMPLATE = [
@@ -55,6 +56,7 @@ def execute
 
               input_variables[:agent_scratchpad] << answer.content.to_s << answer.suggestions.to_s
               tool_class = answer.tool
+              logger.debug(message: "Picked tool", tool: tool_class.to_s)
 
               tool = tool_class.new(
                 context: context,
@@ -76,6 +78,8 @@ def execute
 
           private
 
+          attr_reader :logger
+
           # This method should not be memoized because the input variables change over time
           def prompt
             Utils::Prompt.no_role_text(PROMPT_TEMPLATE, input_variables)
diff --git a/ee/lib/gitlab/llm/chain/answer.rb b/ee/lib/gitlab/llm/chain/answer.rb
index 8e0b0909621cbed86875478daf9c81722a8a08e5..92fe0e6e38d9fd78eba3f990c60d22bfe6160373 100644
--- a/ee/lib/gitlab/llm/chain/answer.rb
+++ b/ee/lib/gitlab/llm/chain/answer.rb
@@ -31,6 +31,8 @@ def self.from_response(response_body:, tools:, context:)
 
           return final_answer(context: context, content: default_final_answer) unless tool
 
+          logger.debug(message: "Answer", content: content)
+
           new(
             status: :ok,
             context: context,
@@ -42,6 +44,8 @@ def self.from_response(response_body:, tools:, context:)
         end
 
         def self.final_answer(context:, content:)
+          logger.debug(message: "Final answer", content: content)
+
           new(
             status: :ok,
             context: context,
@@ -53,10 +57,14 @@ def self.final_answer(context:, content:)
         end
 
         def self.default_final_answer
+          logger.debug(message: "Default final answer")
+
           s_("AI|I don't see how I can help. Please give better instructions!")
         end
 
         def self.error_answer(context:, content:)
+          logger.error(message: "Error", error: content)
+
           new(
             status: :error,
             content: content,
@@ -65,6 +73,10 @@ def self.error_answer(context:, content:)
             is_final: true
           )
         end
+
+        private_class_method def self.logger
+          Gitlab::Llm::Logger.build
+        end
       end
     end
   end
diff --git a/ee/lib/gitlab/llm/chain/tools/issue_identifier.rb b/ee/lib/gitlab/llm/chain/tools/issue_identifier.rb
index f5c1a3b9434ffd297e1089abc062de68eb6a4df2..686003545b69b647ce3fd391172c217555caf27a 100644
--- a/ee/lib/gitlab/llm/chain/tools/issue_identifier.rb
+++ b/ee/lib/gitlab/llm/chain/tools/issue_identifier.rb
@@ -80,7 +80,9 @@ def execute
             return already_identified_answer if already_identified?
 
             MAX_RETRIES.times do
+              logger.debug(message: "Prompt", class: self.class.to_s, content: prompt)
               response = request(prompt)
+
               json = extract_json(response)
               issue = identify_issue(json[:ResourceIdentifierType], json[:ResourceIdentifier])
 
@@ -91,15 +93,20 @@ def execute
               context.resource = issue
 
               content = "I now have the JSON information about the issue ##{issue.iid}."
+
+              logger.debug(message: "Answer", class: self.class.to_s, content: content)
               return Answer.new(status: :ok, context: context, content: content, tool: nil)
             rescue JSON::ParserError
               # try to help out AI to fix the JSON format by adding the error as an observation
               self.retries += 1
 
               error_message = "\nObservation: JSON has an invalid format. Please retry"
+              logger.error(message: "Error", class: self.class.to_s, error: error_message)
+
               options[:suggestions] += error_message
-            rescue StandardError
-              # todo: add exception logging
+            rescue StandardError => e
+              logger.error(message: "Error", error: e.message, class: self.class.to_s)
+
               return Answer.error_answer(context: context, content: _("Unexpected error"))
             end
 
@@ -170,6 +177,7 @@ def prompt
           def already_identified_answer
             resource = context.resource
             content = "You already have identified the issue ##{resource.iid}, read carefully."
+            logger.debug(message: "Answer", class: self.class.to_s, content: content)
 
             ::Gitlab::Llm::Chain::Answer.new(
               status: :ok, context: context, content: content, tool: nil, is_final: false
diff --git a/ee/lib/gitlab/llm/chain/tools/summarize_comments.rb b/ee/lib/gitlab/llm/chain/tools/summarize_comments.rb
index c2f1345c821201b6dba8cb15c96352cde1ba1423..881252450572caf96e57bcc5a64eac75b9bbe293 100644
--- a/ee/lib/gitlab/llm/chain/tools/summarize_comments.rb
+++ b/ee/lib/gitlab/llm/chain/tools/summarize_comments.rb
@@ -23,6 +23,7 @@ def execute
                         "#{resource_name(resource)} ##{resource.iid} has no comments to be summarized."
                       end
 
+            logger.debug(message: "Answer", class: self.class.to_s, content: content)
             ::Gitlab::Llm::Chain::Answer.new(
               status: :ok, context: context, content: content, tool: nil, is_final: false
             )
diff --git a/ee/lib/gitlab/llm/chain/tools/tool.rb b/ee/lib/gitlab/llm/chain/tools/tool.rb
index a39cf174ef26847e51ef0fa3a21333afbd848554..14e23d1888769d0e9f5e365dd1f022883f5b970e 100644
--- a/ee/lib/gitlab/llm/chain/tools/tool.rb
+++ b/ee/lib/gitlab/llm/chain/tools/tool.rb
@@ -15,6 +15,7 @@ class Tool
           def initialize(context:, options:)
             @context = context
             @options = options
+            @logger = Gitlab::Llm::Logger.build
           end
 
           def execute
@@ -36,6 +37,10 @@ def projects_from_context
             end
           end
           strong_memoize_attr :projects_from_context
+
+          private
+
+          attr_reader :logger
         end
       end
     end