diff --git a/danger/ai_logging/Dangerfile b/danger/ai_logging/Dangerfile new file mode 100644 index 0000000000000000000000000000000000000000..18711b9fa1aefc42e15dd74aebb2f8589d8993b8 --- /dev/null +++ b/danger/ai_logging/Dangerfile @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +ai_logging.check_ai_logging diff --git a/danger/plugins/ai_logging.rb b/danger/plugins/ai_logging.rb new file mode 100644 index 0000000000000000000000000000000000000000..2c2064dc324fd45be0005d6ec143b172ec74dbb2 --- /dev/null +++ b/danger/plugins/ai_logging.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require_relative '../../tooling/danger/ai_logging' + +module Danger + class AiLogging < ::Danger::Plugin + # Include the helper code + include Tooling::Danger::AiLogging + end +end diff --git a/spec/tooling/danger/ai_logging_spec.rb b/spec/tooling/danger/ai_logging_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..0c803dcc0dbd8ccf4fab39605686a24b18fd6011 --- /dev/null +++ b/spec/tooling/danger/ai_logging_spec.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' +require 'gitlab/dangerfiles/spec_helper' +require_relative '../../../tooling/danger/ai_logging' +require_relative '../../../tooling/danger/project_helper' + +RSpec.describe Tooling::Danger::AiLogging, feature_category: :service_ping do + include_context "with dangerfile" + + subject(:ai_logging) { fake_danger.new(helper: fake_helper) } + + let(:fake_danger) { DangerSpecHelper.fake_danger.include(described_class) } + let(:fake_project_helper) { instance_double(Tooling::Danger::ProjectHelper) } + let(:modified_files) { ['app/services/ai_service.rb'] } + let(:file_content) { '@logger = Gitlab::Llm::Logger.build; @logger.info("Some AI log")' } + + before do + stub_const('Diff', Struct.new(:patch)) + stub_const('Git', Struct.new(:modified_files, :file_content) do + def diff_for_file(_file) + Diff.new(file_content) + end + end) + + allow(fake_helper).to receive(:git).and_return( + Git.new(modified_files, file_content) + ) + allow(fake_helper).to receive(:stable_branch?).and_return(false) + allow(fake_helper).to receive(:markdown_list).and_return("app/services/ai_service.rb") + end + + describe '#check_ai_logging' do + subject(:check_ai_logging) { ai_logging.check_ai_logging } + + context 'when there are no AI logging issues' do + let(:modified_files) { ['app/models/user.rb'] } + let(:file_content) { 'def some_method; end' } + + it 'does not warn' do + expect(ai_logging).not_to receive(:warn) + check_ai_logging + end + end + + context 'when there are AI logging issues' do + let(:modified_files) { ['app/services/ai_service.rb'] } + let(:file_content) { '@logger = Gitlab::Llm::Logger.build; @logger.info("Some AI log")' } + + it 'warns about non-compliant AI logging' do + expect(ai_logging).to receive(:warn).with(Tooling::Danger::AiLogging::AI_LOGGING_WARNING) + expect(ai_logging).to receive(:markdown).with( + a_string_including(Tooling::Danger::AiLogging::AI_LOGGING_FILES_MESSAGE) + ) + check_ai_logging + end + end + + context 'when there is appropriate AI logging with expanded_ai_logging feature flag' do + let(:modified_files) { ['app/services/ai_service.rb'] } + let(:file_content) { '@logger = Gitlab::Llm::Logger.build; @logger.info("Some AI log with expanded_ai_logging")' } + + it 'does not warn' do + expect(ai_logging).not_to receive(:warn) + check_ai_logging + end + end + end +end diff --git a/tooling/danger/ai_logging.rb b/tooling/danger/ai_logging.rb new file mode 100644 index 0000000000000000000000000000000000000000..8708ef9c496ad1977a32536c7e9d388647130d4c --- /dev/null +++ b/tooling/danger/ai_logging.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module Tooling + module Danger + module AiLogging + AI_LOGGING_WARNING = <<~MSG + ## âš ï¸ Potentially Non-Compliant AI Logging Detected + + This merge request contains AI logging that may not comply with GitLab's AI data usage policies. + Please ensure proper warnings are included and review the [AI logging documentation](https://docs.gitlab.com/ee/development/ai_features/#logs). + + To resolve this: + 1. Ensure you're using `GitLab::Llm::Logger.build.info` or `GitLab::Llm::Logger.build.error` for AI-related logging. + 2. Add appropriate warnings to your AI logging calls. + 3. Ensure you're not logging sensitive or personal information. + 4. Consider if the logging should be gated behind the `expanded_ai_logging` feature flag. + + For more information, see: https://docs.gitlab.com/ee/user/gitlab_duo/data_usage.html + MSG + + AI_LOGGING_FILES_MESSAGE = <<~MSG + The following files contain potentially non-compliant AI logging: + MSG + + def check_ai_logging + return if helper.stable_branch? + + ai_logging_files = find_ai_logging_files + + return unless ai_logging_files.any? + + warn AI_LOGGING_WARNING + markdown(AI_LOGGING_FILES_MESSAGE + helper.markdown_list(ai_logging_files)) + end + + private + + def find_ai_logging_files + helper.git.modified_files.select do |file| + next unless file.end_with?('.rb') + + content = helper.git.diff_for_file(file).patch + + next unless check_logger_or_path(content, file) + + !content.include?('expanded_ai_logging') + end + end + + def check_logger_or_path(file_content, file_path) + logger_pattern = /@logger\s*=\s*Gitlab::Llm::Logger\.build/ + info_or_error_pattern = /logger\.((?:info(?!_or_debug)|error))(.*)$/ + + (file_content.match?(logger_pattern) && file_content.match?(info_or_error_pattern)) || + (file_path.include?("llm") && file_content.match?(info_or_error_pattern)) + end + end + end +end