Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduction de matomo coté serveur #6063

Merged
merged 19 commits into from
May 21, 2021

Conversation

firm1
Copy link
Contributor

@firm1 firm1 commented Mar 5, 2021

Cette PR permet de faire rentrer matomo (anciennement piwik) comme outil de tracking à la place de google analytics.

Pour la petite histoire, j'ai installé matomo sur un serveur tout frais ( 2CPU , 2GB RAM, 20Go) léger pour pouvoir évaluer s'il peut tenir la route. Si jamais on veut l'utiliser pour zeste de savoir, il suffira de créer un sous domaine de zestedesavoir.com et de le faire pointer sur l'IP du serveur.

NB : que l'on choisisse d'heberger matomo sur une infra zds ou via une offre allechante, la PR reste d'actualité puisque l'url matomo est un paramètre au déploiement du site

Ce que fait la PR

  • Intégration de matomo comme outil de tracking des visites sur le site : ce qui permet d'envisager de se débarrasser de notre dépendance à Google Analytics et donc d'avoir des stats plus fiable.

Ce que ne fait pas la PR

  • On n'enlève pas encore Google Analytics (le module de stats des tutos se base encore sur Google Analytics, la refonte sera pour une autre PR), l'idée de la PR est d'abord d'introduire matomo et d'évaluer si les performances tiennent la route (je vais solliciter un déploiement de la PR en beta pour cela)

Ticket concerné : #6030

Contrôle qualité

Idéalement il faudra déployer cette PR sur la beta pour pouvoir tester convenablement si ça n'impacte pas le rendu du site.

@firm1 firm1 added the C-Back Concerne le back-end Django label Mar 5, 2021
@coveralls
Copy link

coveralls commented Mar 5, 2021

Coverage Status

Coverage increased (+0.1%) to 86.736% when pulling cb92bb1 on firm1:migrate_to_matomo into 4a9e862 on zestedesavoir:dev.

Copy link
Member

@AmauryCarrade AmauryCarrade left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C'est cool ! Quelques remarques sur la façon de collecter les données et autres menus détails.

Concernant l'implémentation dans son ensemble, il serait vraiment mieux qu'elle soit asynchrone, afin de ne pas ralentir le chargement des pages. Pour ce faire, plusieurs possibilités :

  1. créer un thread par requête pour envoyer le ping à Matomo ;
  2. créer un thread par worker contenant une file d'attente pour envoyer les pings à Matomo ;
  3. utiliser un paquet Django existant pour 1 ou 2.

Supposant que Django n'exécute qu'une fois le fichier enablematomomiddleware.py de l'intergiciel, il suffirait de créer un thread là, contenant une file d'attente et une boucle infinie de traitement ou que sais-je ; et de lui passer les requêtes à transmettre (ou juste les infos nécessaire pour éviter de faire vivre trop longtemps l'objet Request de Django).

Sur Discord, sgble précisait qu'il fallait être prudent :

Peut-être que Gunicorn attend de ses workers qu'ils n'aient pas de long-lived process ou thread. Cas 1 : il les désactive (uwsgi le fait sur option, par exemple), Cas 2 : il laisse faire mais il faut s'assurer qu'ils ne respawn pas les workers de façon à interférer. Par exemple, je sais que là encore uwsgi peut le faire (pour éviter qu'un worker dégénère avec le temps) et je me dis que Gunicorn aussi, peut-être

Quant à lui, artragis indique qu'il a une idée autre ; je le laisse préciser s'il le désire.

Comment on lines 38 to 44
exclude_paths = ["/contenus", "/mp"]
# only on get
if request.method == "GET":
for p in exclude_paths:
if request.path.startswith(p):
return response
self.matomo_track(request)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tu exlues totalement /contenus et /mp, j'imagine pour des raisons de confidentialité. Sur le principe, je suis d'accord, mais ne serais-ce pas plus pertinent de transmettre une autre URL avec uniquement le début du chemin, ou une URL avec les identifiants retirés, dans ces cas là ?

Ça permettrait de savoir à quel point les MPs et l'édition des contenus est utilisée, sans pour autant savoir quoi (ce qui est une bonne chose). Par exemple, au lieu de transmettre /contenus/stats/3819/zeste-de-savoir-30-elpis-lavant-gout-dune-nouvelle-direction/, on transmettrait /contenus/stats/.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

En fait, je suis parti du principe que "on ne sait jamais" je préfère exclure plutot qu'anonymiser. Au cas ou pour une raison inconnue l'anonymisation ne fonctionne plus correctement. Mais bon, j'avoue que je n'ai pas d'avis tranché.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ne peut-on point appliquer ce même raisonnement à ton filtre, en soit ?

Avec un code d'une complexité similaire (donc le même niveau de risque), on peut obtenir ce que je propose : il suffit de transmettre l'URL de la liste au lieu de bloquer. Et pour obtenir plus de précision tout en ayant un filet, on peut avoir une liste d'exclusion de ce genre, qui serait traitée dans l'ordre en s'arrêtant au premier qui colle :

exclude_paths = [
  # Tout ce qu'on veut voir séparément (pas forcément que ça, c'est pour l'exemple)
  "/contenus/stats",
  "/contenus/historique",
  "/contenus/importer",
  "/contenus/tags",

  # Tous le reste qui sera groupé dans une seule URL (le filet)
  "/contenus",

  # On n'a pas ce souci pour les MPs vu que le détail n'importe que peu
  "/mp"
]

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Du coup, non ?

zds/middlewares/enablematomomiddleware.py Outdated Show resolved Hide resolved
zds/settings/abstract_base/zds.py Outdated Show resolved Hide resolved
@firm1
Copy link
Contributor Author

firm1 commented Mar 7, 2021

Merci @artragis pour la mise en background. C'est mergé sur la branche.

@artragis
Copy link
Member

artragis commented Mar 7, 2021

Après quelques atermoiments, j'ai proposé une "fake PR" pour voir si on peut pas réparer la CI.

J'ai été obligé de désactiver les stats dans cette config, désolé.

Par contre, en contrepartie, je suis repassé à du thread plutôt que du multiprocess. C'est plus léger mais ça reste asynchrone. La seule chose c'est que quand un worker est occupé à envoyer des stats, bah il peut pas prendre des requêtes. Et vice versa. Les stats sont queuées en attendant d'avoir un peu d'espace CPU de libre quand on a une montée en charge des requêtes.

@AmauryCarrade
Copy link
Member

Si l'envoi est différé, alors il faudrait stocker l'heure exacte de la requête initiale et la passer à Matomo via les paramètres h, m et s.

@firm1
Copy link
Contributor Author

firm1 commented Mar 16, 2021

J'ai pris en compte le code d'@artragis sur le traitement asynchrone de l'envoi et la remarque de @AmauryCarrade sur l'heure exacte.

J'en ai profité pour migrer la page des stats des contenus vers matomo. On perd l'information sur le nombre de session mais c'est tout principalement.

Est-ce que la mise à jour de la PR pourraient être déployée sur la beta ?

@Situphen
Copy link
Member

Je me suis permis de mettre à jour ta branche par rapport à upstream/dev pour avoir la même version de zmarkdown sur la bêta et sur la prod :)

zds/middlewares/matomomiddleware.py Outdated Show resolved Hide resolved
zds/middlewares/matomomiddleware.py Outdated Show resolved Hide resolved
@firm1
Copy link
Contributor Author

firm1 commented Mar 29, 2021

J'ai pris en compte les remarques d'artragis ici. un deploiement en beta (@Situphen , @philippemilink ) ?

@Situphen
Copy link
Member

J'ai pris en compte les remarques d'artragis ici. un deploiement en beta (@Situphen , @philippemilink ) ?

La bêta est à jour.

Comment on lines 19 to 48
def _background_process(queue: Queue):
data = queue.get(block=True)
while data:
params = {
"idsite": matomo_site_id,
"action_name": data["r_path"],
"rec": 1,
"apiv": matomo_api_version,
"lang": data["client_accept_language"],
"ua": data["client_user_agent"],
"urlref": data["client_referer"],
"url": data["client_url"],
"h": data["datetime"].hour,
"m": data["datetime"].minute,
"s": data["datetime"].second,
}
requests.get(
matomo_api_url,
params=params,
)
logger.info(f'Matomo tracked this link : {data["client_url"]}')

data = queue.get(block=True)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Je demande surtout à @artragis qui a implémenté ça : si j'ai bien compris, la tâche attend qu'il y ait une charge CPU pas trop importante pour traiter les requêtes. Donc il y aura a priori des requêtes traitées par lot. Ne serait-il pas pertinent d'utiliser l'API de traitement par lot de Matomo ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ça peut être pertinent mais ça augmentera la complexité du code. J'étais juste inconscient que cette API existait.

@AmauryCarrade
Copy link
Member

Au vu de ce genre de profils, je me demande si on ne suit pas les bots. Ça fait beaucoup d'activité tout de même. Matomo ne me montre pas l'UA brute donc je ne peux pas confirmer, mais ça me semble louche.

A passé un total de 23 heures 55 min sur le site web, et effectué 2016 actions (500 Vues de page) en 1 visites.

zds/middlewares/matomomiddleware.py Outdated Show resolved Hide resolved
@firm1
Copy link
Contributor Author

firm1 commented Apr 20, 2021

Remarques des uns et des autres prises en charges (notamment l'envoi de l'IP) et tests corrigé (j'ai mocké complètement l'API dans les tests).

Je pense qu'on arrive au bout de cette PR.

@Situphen
Copy link
Member

Situphen commented Apr 20, 2021

Si tu as le temps de faire un petit rebase ce serait bien ! Je vais mettre à jour la PR sur la bêta :)

@firm1
Copy link
Contributor Author

firm1 commented Apr 20, 2021

Rebase done.

@Situphen
Copy link
Member

Bêta à jour !

Situphen
Situphen previously approved these changes Apr 20, 2021
Copy link
Member

@artragis artragis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

COmme on a requis ma review, j'ai refait une passe et je n'ai qu'une seule et unique remarque.

Pour la corriger on a deux méthodes : soit on invalide le token côté matomo et tout est gagné, soit on retire ça de notre git

zds/settings/abstract_base/zds.py Outdated Show resolved Hide resolved
@philippemilink
Copy link
Member

Pour la corriger on a deux méthodes : soit on invalide le token côté matomo et tout est gagné, soit on retire ça de notre git

Il faut faire les deux, non ? Le token a été exposé publiquement, il faut donc le changer ! Et oui, dans tous les cas le retirer de Git.

@philippemilink
Copy link
Member

Je viens d'aller voir les statistiques d'un de mes billets sur la bêta, et les pages contenant les statistiques sont très longues à charger (20,9 secondes), comme si récupérer les infos de Matomo était très long. Sur la prod, le chargement de ces pages est bien plus rapide (1,8 secondes). Je suis le seul à observer ce phénomène ?

@Situphen
Copy link
Member

Les logs de la bêta m'indiquent un soucis :

Matomo tracked this link : https://beta.zestedesavoir.com/
Matomo tracked this link : https://scaleway.zestedesavoir.com/forums/flux/messages/atom/
Matomo tracked this link : https://beta.zestedesavoir.com/billets/
Matomo tracked this link : https://scaleway.zestedesavoir.com/forums/flux/sujets/atom/

C'est dû à l'utilisation de la fonction request.get_host() qui s'appuie sur ALLOWED_HOSTS qui est probablement trop permissif :

ALLOWED_HOSTS = [
"beta.zestedesavoir.com",
"scaleway.zestedesavoir.com",
"zdsappserver",
"gandi.zestedesavoir.com",
"gandi.zestedesavoir.com.",
".zestedesavoir.com",
".zestedesavoir.com.",
"127.0.0.1",
"localhost",
"163.172.171.246",
]

@firm1
Copy link
Contributor Author

firm1 commented May 17, 2021

Je viens d'aller voir les statistiques d'un de mes billets sur la bêta, et les pages contenant les statistiques sont très longues à charger (20,9 secondes), comme si récupérer les infos de Matomo était très long. Sur la prod, le chargement de ces pages est bien plus rapide (1,8 secondes). Je suis le seul à observer ce phénomène ?

@philippemilink je n'ai pas observé ça chez moi. Tu reproduis encore ce problème ?

@firm1
Copy link
Contributor Author

firm1 commented May 17, 2021

Les logs de la bêta m'indiquent un soucis :

Matomo tracked this link : https://beta.zestedesavoir.com/
Matomo tracked this link : https://scaleway.zestedesavoir.com/forums/flux/messages/atom/
Matomo tracked this link : https://beta.zestedesavoir.com/billets/
Matomo tracked this link : https://scaleway.zestedesavoir.com/forums/flux/sujets/atom/

C'est dû à l'utilisation de la fonction request.get_host() qui s'appuie sur ALLOWED_HOSTS qui est probablement trop permissif :

ALLOWED_HOSTS = [
"beta.zestedesavoir.com",
"scaleway.zestedesavoir.com",
"zdsappserver",
"gandi.zestedesavoir.com",
"gandi.zestedesavoir.com.",
".zestedesavoir.com",
".zestedesavoir.com.",
"127.0.0.1",
"localhost",
"163.172.171.246",
]

@Situphen c'est probablement la consultation via munin qui ne passe pas d'entête http et donc django va chercher le nom du host lui même. Je ne vois pas comment on peut faire autrement.

Copy link
Member

@AmauryCarrade AmauryCarrade left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On m'a demandé une revue, donc acte.

J'ai adressé plusieurs commentaires qui ont été ignorés et qui sont toujours d'actualité, après vérification :

Concernant le second point, ça semble plus subtil que ça car cette fois, on a l'UA — Wget. Donc si quelqu'un aspire l'ensemble du site avec wget (ce qui semble se produire régulièrement ?!), on voit tout dans Matomo. Mais les autres bots plus classiques (Google, etc.) semblent correctement filtrés par Matomo.

Profil utilisateur d'un aspirateur de site dans matomo. Énormément de visites sur un court laps de temps, UA “wget”.

@artragis
Copy link
Member

des statistiques groupées sur certaines URLs plutôt que rien ;

personnellement cette solution ne me convainc pas d'autant que je ne vois pas à quoi serviraient ces stats sinon à augmenter le poids de la bdd

certains bots semblent passer sur Matomo (vérifié à l'instant sur l'instance de prod de Matomo).

ça ressemble à munin : 7 requêtes toutes les 5 minutes pendant 12h ça fait 1008, si on compte que la visite est sensée avoir duré "11 h 45", tu enlèves 2 cycles et tu tombes pile sur... 994

@AmauryCarrade
Copy link
Member

personnellement cette solution ne me convainc pas d'autant que je ne vois pas à quoi serviraient ces stats sinon à augmenter le poids de la bdd

Dans mon esprit, à savoir à quel point les outils de rédaction/MP étaient utilisés, mais ce n'est pas forcément indispensable, certes.

ça ressemble à munin : 7 requêtes toutes les 5 minutes pendant 12h ça fait 1008, si on compte que la visite est sensée avoir duré "11 h 45", tu enlèves 2 cycles et tu tombes pile sur... 994

Ah oui bien vu, d'autant que les pages visitées sont bien moins diversifiées que ce qu'il me semblait — c'est totalement ça. Du coup, il faudrait ou filtrer Wget côté Matomo, si possible, ou le filtrer avant d'envoyer la requête à Matomo.

@firm1
Copy link
Contributor Author

firm1 commented May 20, 2021

@AmauryCarrade je n'avais pas répondu sur ton commentaire, mais je partage l'avis de @artragis ici :

  • Point 1 : je ne suis pas convaincu par l'utilité de rajouter une complexité au code pour avoir des infos sur les outils de rédactions. Ce que je veux dire c'est que je ne perçois pas le gain de savoir que la fonction d'historique (ou d'import) est utilisé x fois, d'autant plus que l'on aura pas la distinction par contenus/auteurs. Je ne trouve pas l'info très exploitable.
  • Point 2 : il s'agit bien de munin, par contre ça pose un dilemme. Théoriquement ce n'est pas le rôle du code de zds d'identifier ce qu'est un bot, c'est le rôle de matomo. Si matomo n'arrive pas à l'identifier est-ce que c'est à nous de le faire ? qu'en sera t-il d'un bot dont l'UA sera curl ? Mais je suis d'accord qu'infine on ne veut pas considérer munin comme une vraie visite. Du coup la question est : faut-il le filter en amont ou en sortie (lorsque l'on veut afficher les résultats) ?

@artragis
Copy link
Member

J'aurais dit en sortie. A priori matomo est capable de se rendre compte que c'est lui et pas un autre alors je me dis qu'on peut carrément blacklister sur matomo l'id de wget

@AmauryCarrade
Copy link
Member

OK pour les deux points. J'approuve le blocage côté Matomo si c'est possible, ça devrait rester son rôle que de filtrer.

@AmauryCarrade
Copy link
Member

J'ai configuré l'instance de production de Matomo pour exclure Wget et CURL.

Exclusion de Wget et CURL via leurs user-agents avec les expressions régulières respectives : /^Wget/[0-9.]+/ et /^curl/[0-9.]+/

Le suivi de Munin semble bien avoir cessé. Donc pour moi, on est bons ici !

@artragis artragis merged commit f8b172d into zestedesavoir:dev May 21, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-Back Concerne le back-end Django
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

6 participants